0

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

1 Answers1

0

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!

Yom T.
  • 8,760
  • 2
  • 32
  • 49
  • The way the html is structured in the original post has to stay intact, unfortunately. I'm not able to change it in any way. Is there still a way to make it work? – MjTheHunter Jan 24 '19 at 15:39
  • I see. Yes, there always is a way. But we will most likely need some script this time. – Yom T. Jan 24 '19 at 15:43
  • Ah ok! So would that have to be done with javascript? I'm sorry, I'm not very knowledgable about this. – MjTheHunter Jan 24 '19 at 16:06
  • I'm working on the fix for you. The real issue seems to be that the parent and child elements are sharing the same classes. – Yom T. Jan 24 '19 at 16:15
  • @MjTheHunter Answer updated. Let me know if that works for you. – Yom T. Jan 24 '19 at 16:48
  • Exactly, that's a really unfortunate thing. I couldn't figure out how to target those individually. – MjTheHunter Jan 24 '19 at 16:49
  • That wonderful! Works like a charm, thank you! One thing I couldn't figure out why, is that the sub categories actually by default are all visible? Is there a way around that? This means that when I load the page, you're actually able to select all the sub categories if you want. Again, thank you so much. I really appreciate it! – MjTheHunter Jan 24 '19 at 17:02
  • @MjTheHunter The children ULs should be hidden initially. Did you add those CSS? Notice it's setting the `display` to `none` when the `data-open="false"` and this is one important piece. Also don't forget to add this same attribute on the parent LIs `
  • `.
  • – Yom T. Jan 24 '19 at 17:05
  • You can choose to have any of the parent LIs to be initially showing its children by setting this `data-open="true"` on HTML level. It's the value of this attribute that we are toggling (between true/false) on checkbox select, which then determines if the children are to be hidden/shown. – Yom T. Jan 24 '19 at 17:07
  • I see what the problem is. Again adding the data-open="false" is not possible, right? As that is again modifying the HTML, unless I'm misunderstanding. Everything else works as expected though. – MjTheHunter Jan 24 '19 at 17:11
  • So you are unable to add this attribute `data-open="false"` on the parent LIs? – Yom T. Jan 24 '19 at 17:12
  • Yes, the original piece of html that I posted is what I have to work with unfortunately. – MjTheHunter Jan 24 '19 at 17:14
  • There still is a way — We could add them with script on document ready. – Yom T. Jan 24 '19 at 17:15
  • You're a legend! Thank you very much for you help on this! I hope you have a wonderful day – MjTheHunter Jan 24 '19 at 17:35
  • No problem. Glad I could help! BTW are you getting some initial flickers on the children though? Because of the `display` state being set dynamically. Hope not. – Yom T. Jan 24 '19 at 17:39
  • Ah yes, I hadn't even noticed that. If I reload the page, yes then they start out unhidden and then quickly becomes hidden – MjTheHunter Jan 24 '19 at 17:56
  • One thing we could do to at least minimize it, is to hide the children until the required attribute is all set and styles applied, then re-display them. – Yom T. Jan 24 '19 at 19:03
  • That worked perfectly! thank you so much for all of your help – MjTheHunter Jan 24 '19 at 22:00
  • I had a look at your other post about adding toggles, where would you add the .slideToggle(state) on the code above? – MjTheHunter Jan 24 '19 at 22:04
  • @MjTheHunter We would add that into the same `change` event handler, however, there may be more work to do besides adding that, such as preventing the same event handler from being triggered on sibling (`li#download_category`) elements, because when we set the `:checked` state of a checkbox, it triggers this event, so that could be an issue. – Yom T. Jan 25 '19 at 01:24
  • 1
    No worries, what I have now works perfectly for what I need :) – MjTheHunter Jan 25 '19 at 01:31