2
\$\begingroup\$
$.fn.linkNestedCheckboxes = function () {
    var childCheckboxes = $(this).find('input[type=checkbox] ~ ul li input[type=checkbox]');
    childCheckboxes.change(function () {
        var checked = $(this).attr('checked');
        checked = checked || ($(this).closest('li').siblings('li').find('input:checked').length > 0)
        childCheckboxes.parent().closest('ul').siblings('input[type=checkbox]').attr('checked', checked);
    });

    var parentCheckboxs = childCheckboxes.parent().closest('ul').siblings('input[type=checkbox]')
    parentCheckboxs.change(function () {
        $(this).siblings('ul').find('input[type=checkbox]').attr('checked', $(this).attr('checked'))
    })
    return $(this);
};

Example of use:

<script type="text/javascript">
    $(document).ready(function () {
        $('form').linkNestedCheckboxes();
    });
</script>

<form>
    <input type="checkbox" name="parent" />
    <ul>
        <li><input type="checkbox" name="child1" /></li>
        <li><input type="checkbox" name="child2" /></li>
    </ul>
</form>

The result of this is:

  • When parent is checked/unchecked all the child checkboxes will be out into the same state
  • If nothing is checked & one of the children are then checked, the parent will be checked
  • When the last child is unchecked the parent will be unchecked

What I'd like to know is:

  • Have I just re-invented the wheel?
  • If not, is there a more efficient way of doing this? The selectors above seem a bit convoluted.
  • This works on my machine, but I haven't tested it beyond my limited requirements for one form, will this break anything other than my simplistic example HTML?
\$\endgroup\$
  • \$\begingroup\$ Actually this doesn't work properly even in your example. When you click the parent then uncheck both the children then uncheck the oarent it will then mark the children checked. \$\endgroup\$ – James Khoury Sep 9 '11 at 4:07
  • \$\begingroup\$ I'm not seeing that behaviour, when I uncheck both the children, the parent is unchecked. Clicking on the parent after that checks both the children, which is the expected behaviour. What browser are you using? \$\endgroup\$ – Glenn Slaven Sep 9 '11 at 13:11
  • \$\begingroup\$ i'm using chrome. \$\endgroup\$ – James Khoury Sep 10 '11 at 11:02
  • \$\begingroup\$ jsfiddle.net/skjY9 try checking them all then unchecking them. \$\endgroup\$ – James Khoury Sep 10 '11 at 11:13
  • \$\begingroup\$ Ok, I know what the problem is, I'm still using jQuery 1.4.4, something has changed in the latest version \$\endgroup\$ – Glenn Slaven Sep 11 '11 at 22:59
3
\$\begingroup\$

Firstly use .prop not .attr if you are using jQuery v1.6+.

Secondly your code has a bug in it. When you click the parent then uncheck both the children then uncheck the parent it will then mark the children checked even though the parent is unchecked.

If you mean that the parent is "checked" when any of the children are "checked":

http://jsfiddle.net/FS3CC/1/

$.fn.linkNestedCheckboxes = function () {
    var childCheckboxes = $(this).find('input[type=checkbox] ~ ul li input[type=checkbox]');

    childCheckboxes.change(function(){
        var parent = $(this).closest("ul").prevAll("input[type=checkbox]").first();
        var anyChildrenChecked = $(this).closest("ul").find("li input[type=checkbox]").is(":checked");
        $(parent).prop("checked", anyChildrenChecked);
    });

    // Parents
    childCheckboxes.closest("ul").prevAll("input[type=checkbox]").first()
        .change(function(){
            $(this).nextAll("ul").first().find("li input[type=checkbox]")
                .prop("checked", $(this).prop("checked"));        
        });

    return $(this);
};

Firstly in the //Parents change event you want to set the children to what ever the value of the parent no matter what.

This is the parent element:

// Parents
childCheckboxes.closest("ul").prevAll("input[type=checkbox]").first()

its change event means set all the child check boxes to the same value

.prop("checked", $(this).prop("checked"));

The child check box change event is slightly more complicated but basically it finds any child check boxes that are checked and sets the parent accordingly.

(if you meant that the parent should be check ONLY when all the check boxes are checked its slightly different so please comment as such.)

A different approach would be to specify who the parent is on the element:

<form>
    <input type="checkbox" name="parent" id="parent" />
    <ul>
        <li><input type="checkbox" name="child1" data-parent="parent" />
        <li><input type="checkbox" name="child2" data-parent="parent" />
    </ul>
</form>

Which would make the selectors easier:

(from child)

var parent = $("#" + $(this).data("parent"));

(from parent)

var children = $("input[data-parent=" + $(this).attr("id") + "]");
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.