1

I have nested unordered lists, with a list item as it's heading that looks something like this.

<ul>
    <li><input type="checkbox" /> Header 1</li>
    <ul>
        <li><input type="checkbox" value="1" />Item 1</li>
    </ul>
    <li><input type="checkbox" /> Header 2</li>
    <ul>
        <li><input type="checkbox" value="1" />Item 1</li>
        <li><input type="checkbox" value="2" />Item 2</li>
        <li><input type="checkbox" value="3" />Item 3</li>
        <li><input type="checkbox" value="4" />Item 4</li>
    </ul>
    <li><input type="checkbox" /> Header 3</li>
    <ul>
        <li><input type="checkbox" value="5" />Item 5</li>
        <li><input type="checkbox" value="6" />Item 6</li>
        <li><input type="checkbox" value="7" />Item 7</li>
        <li><input type="checkbox" value="8" />Item 8</li>
        <li><input type="checkbox" value="9" />Item 9</li>
        <li><input type="checkbox" value="10" />Item 10</li>
        <li><input type="checkbox" value="11" />Item 11</li>
        <li><input type="checkbox" value="12" />Item 12</li>
        <li><input type="checkbox" value="13" />Item 13</li>
        <li><input type="checkbox" value="14" />Item 14</li>
        <li><input type="checkbox" value="15" />Item 15</li>
        <li><input type="checkbox" value="16" />Item 16</li>
    </ul>
</ul>

Notice that Item 1 is found in Header 1's list, and Header 2's list.

There are a few things that I am trying to accomplish.

  1. When I click on an header item, it will automatically select or de-select all list items li within it's ul list immediately following it.
  2. When I click a list item li within an nested ul I want to check to see if the other items are all checked (in this case, place a check mark on the Header List Item), or unchecked (in this case, remove the check mark on the Header List Item) if it's a mix of checked and unchecked then set the Header List Item to Indeterminate state.
  3. When I check a List Item, such as List Item 1 that shows up in Header 1's list and Header 2's list, I want it to uncheck both items, or check both items in both lists, and cause the check in requirement two to take place on the effected lists.

I'm having a really hard time of doing this because I'm not really all that good at JavaScript and all of the code I've read thus far does not really come close to what I need.

Any help would be appreciated. I am using jQuery 1.9.1, but if you want to do it in bare bones JavaScript that is fine with me as well (as it's easier to follow when you set a brake point in Google Dev Tools to see what is going on).

Mark Tomlin
  • 8,593
  • 11
  • 57
  • 72

1 Answers1

2

I have added topList class to the outer UL and header class to LI header elements to simplify the selectors so you might want to tweak that, but I think this should do what you want. Complete example on jsfiddle

//selects all items under a header. This might also select checkboxes from other headers if they share a value. Assumes, "this" points to the header LI
function selectAll() {
    var $this = $(this);
    var isChecked = $this.find(":checkbox").prop("checked");
    $this.find("+ul :checkbox").each(function() {
        selectCheckbox($(this), isChecked);
    });
    $(".header + ul").each(updateHeaderState);
}

//selects or deselects a checkbox. Also, selects other checkboxes if they share a value
function selectCheckbox($checkbox, isChecked) {
    var value = $checkbox.val();
    $checkbox.prop("checked", isChecked);
    $(".topList :checkbox[value="+value+"]").prop("checked", isChecked);
}

//updates the state of the header checkbox. Assumes "this" points to the UL element following a header
function updateHeaderState() {
    var $this = $(this);
    var someChecked = $this.find(":checkbox:checked").length > 0;
    var someUnchecked = $this.find(":checkbox:not(:checked)").length > 0;

    var $header = $this.prev().find(":checkbox");
    $header.prop("indeterminate", someChecked && someUnchecked);
    $header.prop("checked", someChecked || !someUnchecked);
}

$(".header").click(selectAll);
$(".header + ul li").click(function() {
    var $this = $(this);
    var isChecked = $this.find(":checkbox").prop("checked");
    selectCheckbox($this.find(":checkbox"), isChecked);    
    $(".header + ul").each(updateHeaderState);
});
igoratron
  • 134
  • 3
  • Beautiful work! Thank you, I'll take this apart later on, and see if I can't improve my own skills with this example code. You sir, are awesome. – Mark Tomlin Mar 05 '13 at 14:53
  • Down voted for a couple reasons: 1) Your HTML is incorrect. You are nesting UL within a UL, instead of UL inside of an LI (eg. yours == UL > UL, correct == UL > LI > UL) and 2) you are selecting/deselecting checkboxes by value instead of ensuring that only the children of a header checkbox are checked, when the header is checked. Good work on most of it for practice sake, but this code is not HTML compliant or usable in the real world. Here is a link to clarify the nesting of UL's : http://stackoverflow.com/a/5899394/225230 – revive Aug 11 '14 at 18:21