对双向级联复选框使用jQuery

Using jQuery for bidirectional cascading checkboxes

本文关键字:jQuery 复选框 级联      更新时间:2023-09-26

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

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

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

  1. 单击"全选"可切换代码段中所有复选框的状态
  2. 单击矩阵中较深的复选框将切换所有"子复选框"。例如,点击Group1复选框将切换组下面的结构中所有复选框的状态
  3. 如果在给定的组中取消选中任何复选框,那么也必须取消选中组复选框(虽然从技术上讲,代表"部分"填充的三态复选框会很好,但这超出了我的问题范围)。例如,假设项目1-1、1-2和1-3都已检查。然后应选中组1,在本例中还应选中"全选"。然而,如果我取消选择项目1-2,现在组1不是"完整的",因此应该取消选中,"全选"也是如此

好的,我有一些jQuery代码可以用于下面的标记。它运行良好。但它很丑陋。我讨厌丑陋,觉得我的逻辑、选择器和/或HTML标记中有一个缺陷,导致事情变得如此丑陋,而且还有更好的方法。

我很想得到一些关于更好的方法的反馈。就我个人而言,我不必担心超过2级的深度,但通用的N级深度功能应该不会太难。事实上,如果不是第三种情况,即我检查"孩子"的状态,看看是否所有东西都被检查/未检查,这将相对简单。事实上,我正在做一个3次循环(因为评估的顺序)让我很难过。

感谢您的协助。:)

您会注意到我使用的是:disabled check。在我的最后一个项目中,我将一些复选框设置为禁用,因此它们对我上面列出的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);
                        }
                    });
                }
            }); 

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

这是更新后的JSFiddle。我已经评论了JS来解释它是如何工作的。

对于您的标记,我认为将<ul>直接嵌套在另一个<ul>中而不将其封装在<li>中不是一个好的做法(有关更多信息,请参阅此堆栈溢出问题)。此外,如果您可以完全控制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完全在小提琴里,我不会发布它来帮助使它更简短。

希望这能有所帮助。

干杯

我在尝试实现相同的东西时偶然发现了您的问题。

以下是我解决问题的方法: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