jqueryhtmlwordpresscheckboxeasy-digital-downloads

JQuery Show/Hide UL children and unselect other parents checkbox


I'm going to do my best to describe my problem. This community has helped me before, so I'm hoping someone will be kind enough to lend me their help once again. I don't really know much about JQuery.

We're setting up a wordpress site using Easy Digital Downloads, because of that we have some restrictions, like not being able to change the HTML, but we can add add JQuery elements on a page.

This is for a category selection system where a user should be able to select only 1 parent category and multiple sub categories under that parent.

I'm looking for a JQuery solution for the following scenario.

We have 5, potentially 6, "categories". These categories have sub categories represented by a UL. By default the sub categories should be hidden. When you click a parent category it should display the sub categories below. If you at any point click the same parent category, it should unselect all children and hide them again.

I also only want 1 parent category to be selected at any given point.

Let me give you a scenario. You click on Cat A, it expands and shows 4 sub categories. You click 2 of those sub categories. You change your mind and instead click on Cat B. This should then hide the sub categories of Cat A and unselect the children, as well as the parent of Cat A.

Just if I haven't made it clear enough, it's important that you can never select a sub category without a parent category.

I've made a basic fiddle with something I found in another thread. Just is just for showing and hiding (though I haven't added a class for hiding yet) This has the html structure we're using.

Another issue is that all of the parent categories use the same class for the children (.children)

$('#in-download_category-156').click(function() {
  $(".children").toggle(this.checked);
});

FIDDLE

I know this is a big ask, so I appreciate any help you can throw my way! Thank you


Solution

  • Try this (I've added some notes down below):

    $('.fes-category-checklist > li > input[name^="download_category"]').change(function() {
      $(this)
        .closest('ul')
        .find('input[name^="download_category"]')
        .not(this)
        .prop('checked', false);
    });
    ul {
      list-style: none;
      padding-left: 20px;
    }
    
    ul.fes-category-checklist > li > input[name^="download_category"]:not(:checked) ~ ul.children {
      display: none;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <ul class="fes-category-checklist">
      <li id="download_category-156">
        <input value="156" type="checkbox" name="download_category[]" id="in-download_category-156" />
        <label for="in-download_category-156" class="selectit">cat a</label>
    
        <ul class="children">
          <li id="download_category-161"><label class="selectit"><input value="161" type="checkbox" name="download_category[]" id="in-download_category-161"> cat a-1</label></li>
    
          <li id="download_category-162"><label class="selectit"><input value="162" type="checkbox" name="download_category[]" id="in-download_category-162"> cat a-2</label></li>
    
          <li id="download_category-163"><label class="selectit"><input value="163" type="checkbox" name="download_category[]" id="in-download_category-163"> cat a-3</label></li>
    
          <li id="download_category-183"><label class="selectit"><input value="183" type="checkbox" name="download_category[]" id="in-download_category-183"> cat a-4</label></li>
        </ul>
      </li>
    
      <li id="download_category-160">
        <input value="160" type="checkbox" name="download_category[]" id="in-download_category-160" />
        <label for="in-download_category-160" class="selectit">cat b</label>
    
        <ul class="children">
          <li id="download_category-198"><label class="selectit"><input value="198" type="checkbox" name="download_category[]" id="in-download_category-198"> cat b-1</label></li>
    
          <li id="download_category-199"><label class="selectit"><input value="199" type="checkbox" name="download_category[]" id="in-download_category-199"> cat b-2</label></li>
    
          <li id="download_category-200"><label class="selectit"><input value="200" type="checkbox" name="download_category[]" id="in-download_category-200"> cat b-3</label></li>
    
          <li id="download_category-201"><label class="selectit"><input value="201" type="checkbox" name="download_category[]" id="in-download_category-201"> cat b-4</label></li>
        </ul>
      </li>
    
      <li id="download_category-155">
        <input value="155" type="checkbox" name="download_category[]" id="in-download_category-155" />
        <label for="in-download_category-155" class="selectit">cat c</label>
    
        <ul class="children">
          <li id="download_category-164"><label class="selectit"><input value="164" type="checkbox" name="download_category[]" id="in-download_category-164"> cat c-1</label></li>
    
          <li id="download_category-165"><label class="selectit"><input value="165" type="checkbox" name="download_category[]" id="in-download_category-165"> cat c-2</label></li>
    
          <li id="download_category-166"><label class="selectit"><input value="166" type="checkbox" name="download_category[]" id="in-download_category-166"> cat c-3</label></li>
    
          <li id="download_category-169"><label class="selectit"><input value="169" type="checkbox" name="download_category[]" id="in-download_category-169"> cat c-4</label></li>
    
          <li id="download_category-171"><label class="selectit"><input value="171" type="checkbox" name="download_category[]" id="in-download_category-171"> cat c-5</label></li>
    
          <li id="download_category-168"><label class="selectit"><input value="168" type="checkbox" name="download_category[]" id="in-download_category-168"> cat c-6</label></li>
    
          <li id="download_category-170"><label class="selectit"><input value="170" type="checkbox" name="download_category[]" id="in-download_category-170"> cat c-7</label></li>
    
          <li id="download_category-202"><label class="selectit"><input value="202" type="checkbox" name="download_category[]" id="in-download_category-202"> cat c-8</label></li>
        </ul>
      </li>
    
      <li id="download_category-157">
        <input value="157" type="checkbox" name="download_category[]" id="in-download_category-157" />
        <label class="selectit" for="in-download_category-157">cat d</label>
      </li>
    </ul>

    Note that I'm moving the first checkbox groups out of the label and assigning a for attribute on them.

    To associate the <label> with an <input> element in the way shown in the example above, you need to give the <input> an id attribute. The <label> then needs a for attribute whose value is the same as the input's id.

    Also using some CSS trick that allows you to "style" any adjacent siblings based on the pseudo-class of an element, in our case: A checkbox element with :checked state. Since our ul.childrens are not an immediate sibling of the checkboxes, instead of a child combinator we need to use general sibling combinator to target them for styling. This helps in that you do not need any Javascript for toggling the visibility state of the sub-category ULs.


    Edits

    OK, so since the HTML structure has to stay intact and the fact that the parent and child elements are sharing the same classes, we probably don't have much choice but to use chained child combinators, because we don't want any of the children to get the same styles as the parent. Unless you are able to assign some special class or attribute (e.g. <li id="download_category-156" class="parent-category">) on the parents to set them apart from the children.

    This will look a bit ugly (longer selector), but it should address the problem:

    $('.fes-category-checklist > li > label.selectit > input[name^="download_category"]').change(function() {
      var categoryId = this.value;
      var $checkbox = $(this);
    
      $('.fes-category-checklist')
        .find('li[id^="download_category"]')
        .attr('data-open', function(index, value) {
          return this.id.endsWith(categoryId) && value === 'false';
        });
    
      $checkbox
        .closest('ul')
        .find('input[name^="download_category"]')
        .not(this)
        .prop('checked', false);
    });
    
    $(document).ready(function() {
      $('ul.fes-category-checklist')
        .find('> li[id^="download_category"]')
        .attr('data-open', false)
        .fadeIn();
    });
    ul {
      list-style: none;
      padding-left: 20px;
    }
    
    ul.fes-category-checklist > li {
      display: none;
    }
    
    li[data-open="false"] > ul.children {
      display: none;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    
    <ul class="fes-category-checklist">
      <li id="download_category-156">
        <label class="selectit">
          <input value="156" type="checkbox" name="download_category[]" id="in-download_category-156"> cat a
        </label>
        <ul class="children">
          <li id="download_category-161"><label class="selectit"><input value="161" type="checkbox" name="download_category[]" id="in-download_category-161"> cat a-1</label></li>
    
          <li id="download_category-162"><label class="selectit"><input value="162" type="checkbox" name="download_category[]" id="in-download_category-162"> cat a-2</label></li>
    
          <li id="download_category-163"><label class="selectit"><input value="163" type="checkbox" name="download_category[]" id="in-download_category-163"> cat a-3</label></li>
    
          <li id="download_category-183"><label class="selectit"><input value="183" type="checkbox" name="download_category[]" id="in-download_category-183"> cat a-4</label></li>
        </ul>
      </li>
    
      <li id="download_category-160">
        <label class="selectit">
          <input value="160" type="checkbox" name="download_category[]" id="in-download_category-160"> cat b
        </label>
        <ul class="children">
          <li id="download_category-198"><label class="selectit"><input value="198" type="checkbox" name="download_category[]" id="in-download_category-198"> cat b-1</label></li>
    
          <li id="download_category-199"><label class="selectit"><input value="199" type="checkbox" name="download_category[]" id="in-download_category-199"> cat b-2</label></li>
    
          <li id="download_category-200"><label class="selectit"><input value="200" type="checkbox" name="download_category[]" id="in-download_category-200"> cat b-3</label></li>
    
          <li id="download_category-201"><label class="selectit"><input value="201" type="checkbox" name="download_category[]" id="in-download_category-201"> cat b-4</label></li>
        </ul>
      </li>
    
      <li id="download_category-155">
        <label class="selectit">
          <input value="155" type="checkbox" name="download_category[]" id="in-download_category-155"> cat c
        </label>
        <ul class="children">
          <li id="download_category-164"><label class="selectit"><input value="164" type="checkbox" name="download_category[]" id="in-download_category-164"> cat c-1</label></li>
    
          <li id="download_category-165"><label class="selectit"><input value="165" type="checkbox" name="download_category[]" id="in-download_category-165"> cat c-2</label></li>
    
          <li id="download_category-166"><label class="selectit"><input value="166" type="checkbox" name="download_category[]" id="in-download_category-166"> cat c-3</label></li>
    
          <li id="download_category-169"><label class="selectit"><input value="169" type="checkbox" name="download_category[]" id="in-download_category-169"> cat c-4</label></li>
    
          <li id="download_category-171"><label class="selectit"><input value="171" type="checkbox" name="download_category[]" id="in-download_category-171"> cat c-5</label></li>
    
          <li id="download_category-168"><label class="selectit"><input value="168" type="checkbox" name="download_category[]" id="in-download_category-168"> cat c-6</label></li>
    
          <li id="download_category-170"><label class="selectit"><input value="170" type="checkbox" name="download_category[]" id="in-download_category-170"> cat c-7</label></li>
    
          <li id="download_category-202"><label class="selectit"><input value="202" type="checkbox" name="download_category[]" id="in-download_category-202"> cat c-8</label></li>
        </ul>
      </li>
    
      <li id="download_category-157">
        <label class="selectit"><input value="157" type="checkbox" name="download_category[]" id="in-download_category-157"> cat d</label>
      </li>
    </ul>

    Adding the initial state of children visibility

    The following lines will set them to be hidden initially. You could also pass a function in the second argument should you want to adjust the states individually.

    $(document).ready(function() {
      $('ul.fes-category-checklist')
        .find('> li[id^="download_category"]')
        .attr('data-open', false)
        .fadeIn();
    });
    

    Also, if you want give the children ULs some fancy effect while they're being toggled, check out my other post on how you could do that with jQuery slideToggle.

    Hope that helps!