使用jQuery进行双向级联复选框

时间:2014-09-12 21:45:43

标签: javascript jquery checkbox

考虑以下HTML片段:这基本上是一组嵌套的复选框,使用无序列表标记来创建一般分组。代码是动态生成的,因此我可以根据需要完全控制标记。

表示以下问题的jsfiddle位于:http://jsfiddle.net/bwh4rj3h/

我正在尝试创建执行以下操作的jQuery:

  1. 单击全选以切换代码段中所有复选框的状态。
  2. 点击矩阵中较深的复选框将切换所有“子复选框”。例如单击Group1复选框将切换组下方构造中所有复选框的状态。
  3. 如果在给定组中取消选中任何复选框,则还必须取消选中组复选框(虽然从技术上讲,表示“部分”填充的三态复选框会很好,这超出了我的问题的范围)。例如说项目1-1,1-2和1-3都被检查。然后应检查组1,在此示例中还应检查“全选”。但是,如果我取消选择项目1-2,现在组1不是“完整”,因此应该取消选中,“全选”也应如此。
  4. 好的,所以我有一些适用于下面标记的jQuery代码。它工作正常。但它很难看。而且我讨厌丑陋,觉得我的逻辑,选择器和/或HTML标记存在缺陷,导致事情变得丑陋,并且有更好的方式。

    很想得到一些关于更好的方式的反馈。在我个人的情况下,我不必担心超过2级,但一般的N级深层功能不应该太难。事实上,如果不是第三个条件,我正在检查“孩子们”的状态,看看是否所有东西都被检查/取消,那就相对简单了。事实上,我正在进行3次循环(因为评估顺序)让我感到难过。

    提前感谢您的协助。 :)

    你会注意到我正在使用:禁用检查。在我的最后一个项目中,我设置了一些禁用的复选框,因此它们不受上面列出的3个要求的影响。由于标记不包含任何禁用的项目,因此不应影响结果。

    <ul>
       <li>
           <input type="checkbox" id="chkAll" checked/><label for="chkAll">Select All</label>
       </li>
       <ul>
           <li>
               <input type="checkbox" id="chkGroup1" checked/><label for="chkGroup1">Group 1</label>
           </li>
           <ul>
               <li>
                   <input type="checkbox" id="chkGp1-Item1" checked/><label for="chkGp1-Item1">Item 1</label>
               </li>
               <li>
                   <input type="checkbox" id="chkGp1-Item2" checked/><label for="chkGp1-Item2">Item 2</label>
               </li>
           </ul>
        </ul>
    </ul>
    

    jQuery:

      $("input[type=checkbox]:not(:disabled)").on("change", function () {
                    $(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled)").prop("checked", $(this).prop("checked"));
    
                    // Looping 3 times to cover all sublevels.  THERE HAS GOT TO BE A BETTER WAY THAN THIS.
    
                    for (var i = 0; i < 3; i++) {
    
                        $("input[type=checkbox]:not(:disabled)").each(function() {
    
                            var uncheckedkidcount = $(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled):not(:checked)").length;
                            var kidcount = $(this).parent().next("ul").find("li input[type=checkbox]:not(:disabled)").length;
    
                            if (kidcount > 0) {
                                $(this).prop("checked", uncheckedkidcount == 0);
                            }
                        });
                    }
                }); 
    

2 个答案:

答案 0 :(得分:2)

我已经使用不同的解决方案分叉并更新了您的JSFiddle。我对标记进行了一些更改,我认为这会更容易处理,但jQuery仍然很长。希望这会有所帮助。

Here is the updated JSFiddle。我已经对JS进行了评论,以解释它是如何工作的。

对于您的加价 - 我认为将<ul>直接嵌套在另一个<ul>中而不将其包含在<li>中是不错的做法(请参阅此stack overflow question更多信息。另外,如果你完全控制DOM结构,我会添加一些类名来使jQuery更易于管理:

<ul>
    <li>
        <input type="checkbox" id="chkAll" checked/>
        <label for="chkAll">Select All</label>
    </li>
    <li class="has-nested-list">
        <ul class="nested">
            <li>
                <input type="checkbox" id="chkGroup1" checked/>
                <label for="chkGroup1">Group 1</label>
            </li>
            <li class="has-nested-list">
                <ul class="nested">
                    <li>
                        <input type="checkbox" id="chkGp1-Item1" checked/>
                        <label for="chkGp1-Item1">Item 1</label>
                    </li>
                    <li>
                        <input type="checkbox" id="chkGp1-Item2" checked/>
                        <label for="chkGp1-Item2">Item 2</label>
                    </li>
                </ul>
            </li>
        </ul>
    </li>
</ul>

当有嵌套列表时,为了使这项工作没有多个项目符号,我们可以添加这个简单的CSS规则

.has-nested-list {
    list-style-type: none;
}

jQuery完全在小提琴中,我不会发布它以帮助简化这一点。

希望这有帮助。

干杯

答案 1 :(得分:1)

我在尝试实施同样的事情时偶然发现了你的问题。

以下是我解决问题的方法:https://codepen.io/nexx/pen/VyLLPM

它支持多级嵌套复选框,并遵循问题中提到的逻辑。希望它可以帮助一些人在同样的事情上挣扎。作为初学者,我很乐意听到反馈:)

 <ul class="categories-list">
  <li class="category"><input type="checkbox"><label>Parent category</label>
    <a class='collapse-category' href=''>expand</a>
    <ul class="subcategories">
      <li class="category"><input type="checkbox"><label>Child category </label></li>
      <li class="category"><input type="checkbox"><label>Child category </label></li>
      <li class="category"><input type="checkbox"><label>Child category </label></li>
      <li class="category"><input type="checkbox"><label>Child category </label></li>
      <li class="category"><input type="checkbox">
      <label>Child category with children</label>
        <a class='collapse-category' href=''>expand</a>
        <ul class="subcategories">
          <li class="category"><input type="checkbox"><label>Child category </label></li>
          <li class="category"><input type="checkbox"><label>Child category </label></li>
          <li class="category"><input type="checkbox"><label>Child category </label></li>
          <li class="category"><input type="checkbox"><label>Child category </label></li>
          <li class="category"><input type="checkbox">
          <label>Child category with children</label>
            <a class='collapse-category' href=''>expand</a>
            <ul class="subcategories">
              <li class="category"><input type="checkbox"><label>Child category </label></li>
              <li class="category"><input type="checkbox"><label>Child category </label></li>
              <li class="category"><input type="checkbox"><label>Child category </label></li>
              <li class="category"><input type="checkbox"><label>Child category </label></li>
              <li class="category"><input type="checkbox"><label>Child category </label></li>
            </ul>
          </li>
        </ul>
      </li>
    </ul>
  </li>
</ul> 

的jQuery

$.fn.checkboxParent = function() {
//get parent checkbox element
  var checkboxParent = $(this)
  .parent("li")
  .parent("ul")
  .parent("li")
  .find('> input[type="checkbox"]');
  return checkboxParent;
};

$.fn.checkboxChildren = function() {
  //get checkbox children
  var checkboxChildren = $(this)
  .parent("li")
  .find(' > .subcategories > li > input[type="checkbox"]');
  return checkboxChildren;
};


$.fn.cascadeUp = function() {
  var checkboxParent = $(this).checkboxParent();
  if ($(this).prop("checked")) {
    if (checkboxParent.length) {
      //check if all children of the parent are selected - if yes, select the parent
      //these will be the siblings of the element which we clicked on
      var children = $(checkboxParent).checkboxChildren();
      var booleanChildren = $.map(children, function(child, i) {
        return $(child).prop("checked");
      });
      //check if all children are checked
      function and(a, b) {
        return a && b;
      }
      var allChecked = booleanChildren.reduce(and, true);
      if (allChecked) {
        $(checkboxParent).prop("checked", true);
        $(checkboxParent).cascadeUp();
      }
    }
  } else {
    if (checkboxParent.length) {
      //if parent is checked, becomes unchecked
      $(checkboxParent).prop("checked", false);
      $(checkboxParent).cascadeUp();
    }
  }
};
$.fn.cascadeDown = function() {
  var checkboxChildren = $(this).checkboxChildren();
  if ($(this).prop("checked")) {
    if (checkboxChildren.length) {
      //all children are checked
      checkboxChildren.prop("checked", true);
      checkboxChildren.each(function(index) {
        $(this).cascadeDown();
      });
    }
  } else {
    if (checkboxChildren.length) {
      //children become unchecked
      checkboxChildren.prop("checked", false);
      checkboxChildren.each(function(index) {
        $(this).cascadeDown();
      });
    }
  }
};

$("input[type=checkbox]:not(:disabled)").on("change", function() {
  $(this).cascadeUp();
  $(this).cascadeDown();
});

$(".category a").on("click", function(e) {
  e.preventDefault();
  $(this)
    .parent()
    .find("> .subcategories")
    .slideToggle(function() {
    if ($(this).is(":visible")) $(this).css("display", "flex");
  });
});

css(sass):

.categories-list
  margin: 40px 0px
  padding: 0px 10px
  width: 300px
  .category
    font-size: 14px
    display: flex
    flex-direction: column
    position: relative
    padding: 3px 0px 3px 22px
    border-bottom: 1px solid #f5f5f5
    font-weight: 300
    display: flex
    label
      width: 100%
    a
      color: #95939a
      position: absolute
      right: 0px
      z-index: 1000
    input[type="checkbox"]
      margin: 0px 10px 0px 0px
      position: absolute
      left: 0px
      top: 7px
    .subcategories
      margin-left: 0px
      display: none
      padding: 5px
      flex-direction: column
      .category
        padding-left: 22px
        flex-direction: column
        &:last-child
          border-bottom: none