Nested parent/child checkboxes - working solution

2019-03-19 17:44发布

I have been looking for a 'complete' solution to nesting parent child checkboxes that change state correctly based on a hierarchy.

Most 'solutions' do not work or only work to one level. They also require you to name the checkboxes in a particular way.

This Stack Overflow discussion covers the main points but also provide a good solution discovered by Rory here.

I have tested it within my development project and it works perfectly standalone. However, I am using Bootstrap 2.x and for checkboxes

I have a JSFiddle which shows the working example code, then my version with a disabled parent checkbox and then the bootsrap code version which does not work.

<!DOCTYPE html>
<body>
<!-- Raw working example from site http://css-tricks.com/indeterminate-checkboxes/ -->

<b>Raw working example</b>
<p>
<ul>
    <li>
        <input type="checkbox" name="tall" id="tall">
        <label for="tall">Tall Things</label>
        <ul>
            <li>
                <input type="checkbox" name="tall-1" id="tall-1">
                <label for="tall-1">Buildings</label>
            </li>
            <li>
                <input type="checkbox" name="tall-2" id="tall-2">
                <label for="tall-2">Giants</label>
                <ul>
                    <li>
                        <input type="checkbox" name="tall-2-1" id="tall-2-1">
                        <label for="tall-2-1">Andre</label>
                    </li>
                    <li>
                        <input type="checkbox" name="tall-2-2" id="tall-2-2">
                        <label for="tall-2-2">Paul Bunyan</label>
                        <ul>
                            <li>
                                <input type="checkbox" name="tall-2-2-1" id="tall-2-2-1">
                                <label for="tall-2-2-1">Son</label>
                            </li>
                            <li>
                                <input type="checkbox" name="tall-2-2-2" id="tall-2-2-2">
                                <label for="tall-2-2-2">Daughter</label>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li>
                <input type="checkbox" name="tall-3" id="tall-3">
                <label for="tall-3">Two sandwiches</label>
            </li>
        </ul>
    </li>
    <li>
        <input type="checkbox" name="short" id="short">
        <label for="short">Short Things</label>
        <ul>
            <li>
                <input type="checkbox" name="short-1" id="short-1">
                <label for="short-1">Smurfs</label>
            </li>
            <li>
                <input type="checkbox" name="short-2" id="short-2">
                <label for="short-2">Mushrooms</label>
            </li>
            <li>
                <input type="checkbox" name="short-3" id="short-3">
                <label for="short-3">One Sandwich</label>
            </li>
        </ul>
    </li>
</ul>

<hr>

<!-- Non Bootstrap Example -->
<b>My initial code example - Is Working</b>
<p>
<ul>
    <li>

        <input type="checkbox" name="" value="" disabled><strong>Ford</strong>

        <ul>
            <li>

                <input type="checkbox" name="" value="">Fiesta</label>
            </li>
            <li>

                <input type="checkbox" name="" value="">Focus</label>
            </li>
            <li>

                <input type="checkbox" name="" value="">Mondeo</label>
            </li>
        </ul>
    </li>
    <li>

        <input type="checkbox" name="" value="" disabled><strong>Vauxhall</strong>


        <ul>
            <li>

                    <input type="checkbox" name="" value="">Corsa</label>
            </li>
            <li>

                    <input type="checkbox" name="" value="">Astra</label>
            </li>
            <li>

                    <input type="checkbox" name="" value="">Vectra</label>
            </li>
        </ul>
    </li>
</ul>

            <hr>

<!-- Bootstrap Example -->

<b>Bootstrap based code example - Not Working</b>
<p>

<ul>
    <li>
        <label class="checkbox">
            <input type="checkbox" name="" value="" disabled><strong>Ford</strong>

        </label>
        <ul>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Fiesta</label>
            </li>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Focus</label>
            </li>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Mondeo</label>
            </li>
        </ul>
    </li>
    <li>
        <label class="checkbox">
            <input type="checkbox" name="" value="" disabled><strong>Vauxhall</strong>

        </label>
        <ul>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Corsa</label>
            </li>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Astra</label>
            </li>
            <li>
                <label class="checkbox">
                    <input type="checkbox" name="" value="">Vectra</label>
            </li>
        </ul>
    </li>
</ul>
$(function () {
  // Apparently click is better chan change? Cuz IE?
  $('input[type="checkbox"]').change(function (e) {
      var checked = $(this).prop("checked"),
          container = $(this).parent(),
          siblings = container.siblings();

      container.find('input[type="checkbox"]').prop({
          indeterminate: false,
          checked: checked
      });

      function checkSiblings(el) {
          var parent = el.parent().parent(),
              all = true;

          el.siblings().each(function () {
              return all = ($(this).children('input[type="checkbox"]').prop("checked") === checked);
          });

          if (all && checked) {
              parent.children('input[type="checkbox"]').prop({
                  indeterminate: false,
                  checked: checked
              });
              checkSiblings(parent);
          } else if (all && !checked) {
              parent.children('input[type="checkbox"]').prop("checked", checked);
              parent.children('input[type="checkbox"]').prop("indeterminate", (parent.find('input[type="checkbox"]:checked').length > 0));
              checkSiblings(parent);
          } else {
              el.parents("li").children('input[type="checkbox"]').prop({
                  indeterminate: true,
                  checked: false
              }); 
          } 
      } 

      checkSiblings(container); 
  }); 
});

My understanding is that the code needs to be changed somewhere to use parents or closest. Can someone who is a much better code please help identify where the change needs to happen to get the Bootstrap version working.

2条回答
仙女界的扛把子
2楼-- · 2019-03-19 18:23

Take a look at the free JSTree user control. It is JavaScript-based, open source and allows to be configured for multiple styles of tree views, for example:

enter image description here

Since the checkboxes displayed here are just images, you can replace them by radio buttons easily, and via event handling you can disallow multiple selections so they behave like radio buttons if required.

This is achieved by specifying the "checkbox" plugin as follows (see the snippet I provided below for the complete working code):

$(function () {
    $("#demo1")
        .jstree({
        "themes": {
            "theme": "apple","dots": false,"icons": false
        },
        "plugins": ["themes", "html_data", "checkbox", "sort", "ui"]
    });
});

The corresponding HTML structure looks like this, just include it in the body of your page:

<div id="demo1" class="demo" style="height:100px;">
    <ul>
        <li id="phtml_1">   <a href="#">Root node 1</a>
            <ul>
                <li id="phtml_2">   <a href="#">Child node 1</a>
                </li>
                <li id="phtml_3">   <a href="#">Child node 2</a>
                </li>
            </ul>
        </li>
        <li id="phtml_4">   <a href="#">Root node 2</a>
        </li>
    </ul>
</div>

To get the checked values, use this function (assuming you have created a div with id="listForDisplay" as I did in the JSFiddle example):

var $listForDisplay = $("#listForDisplay");
function displayList(data) { // see: https://www.jstree.com/docs/events/
    var i, j, r = [];
    for (i = 0, j = data.selected.length; i < j; i++) {
        r.push(data.instance.get_node(data.selected[i]).text);
    }
    $listForDisplay.html('Selected: ' + r.join(', '));
}

This code can either be triggered in a click event or you can bind JSTree events like I did. Appending the following to the previous JavaScript snippet does the job:

    $("#demo1")
    .on('loaded.jstree', function (e, data) {
            displayList(data);
        })
    .on('change_state.jstree', function (e, data) {
            displayList(data);
        });

Note: When a parent node is selected, the parent name is listed along with the childs. If only the childs are selected, the related parent(s) will not be listed.

You can find more about the options specifying the behaviour of this plugin here.


Runnable code snippet (complete code)

$(function() {

  var $demo1 = $("#demo1");
  
  var $listForDisplay = $("#listForDisplay");
  function displayList(data) { // see: https://www.jstree.com/docs/events/
    var i, j, r = [];
    for (i = 0, j = data.selected.length; i < j; i++) {
      r.push(data.instance.get_node(data.selected[i]).text);
    }
    $listForDisplay.html('Selected: ' + r.join(', '));
  }

  $demo1.jstree({
    "themes": {
      "theme": "apple",
      "dots": false,
      "icons": false
    },
    "plugins": ["themes", "html_data", "checkbox", "sort", "ui"]
  });
  $demo1.on('loaded.jstree', function(e, data) {
    displayList(data);
  });
  $demo1.on('changed.jstree', function(e, data) {
    displayList(data);
  });
});
<head>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

  <link href="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/themes/default/style.min.css" rel="stylesheet" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jstree/3.2.1/jstree.min.js"></script>
</head>

<body>
  <div id="demo1" class="demo" style="height:100px;">
    <ul>
      <li id="phtml_1"> <a href="#">Root node 1</a>
        <ul>
          <li id="phtml_2"> <a href="#">Child node 1</a>
          </li>
          <li id="phtml_3"> <a href="#">Child node 2</a>
          </li>
        </ul>
      </li>
      <li id="phtml_4"> <a href="#">Root node 2</a>
      </li>
    </ul>
  </div>
  <br/>
  <div id="listForDisplay" />
</body>

查看更多
聊天终结者
3楼-- · 2019-03-19 18:30

you can try something like this

  $(function () {
  $('input[type="checkbox"]').change(function (e) {
      var checked = $(this).prop("checked"),
          container = $(this).closest("li"),//get closest li instead of parent
          siblings = container.siblings();
      container.find('input[type="checkbox"]').prop({
          indeterminate: false,
          checked: checked
      });

      function checkSiblings(el) {
          var parent = el.parent().parent(),
              all = true,
              parentcheck=parent.children("label");//get the label that contains the disabled checkbox
          el.siblings().each(function () {
              return all = ($(this).find('input[type="checkbox"]').prop("checked") === checked);
          });
          //use parentcheck instead of parent to get the children checkbox
          if (all && checked) {
              parentcheck.children('input[type="checkbox"]').prop({
                  indeterminate: false,
                  checked: checked
              });
              checkSiblings(parent);
          } else if (all && !checked) {
              parentcheck.children('input[type="checkbox"]').prop("checked", checked);
              parentcheck.children('input[type="checkbox"]').prop("indeterminate", (parent.find('input[type="checkbox"]:checked').length > 0));
              checkSiblings(parent);
          } else {
             parentcheck.children('input[type="checkbox"]').prop({
                  indeterminate: true,
                  checked: false
              });
          }
      }
      checkSiblings(container);
  });
});    

http://jsfiddle.net/Mvs87/2/

查看更多
登录 后发表回答