8

I have a script that will check and uncheck all children checkboxes in a nested list. I am now trying to get it so I can check a low level checkbox and it will check all the parents only back up to the highest level. Here is a JSFiddle

<ul class="tree" id="tree">

    <li><input type="checkbox" name="account_settings" value="yes">Account Settings <!-- AND SHOULD CHECK HERE -->
        <ul>
            <li><input type="checkbox" name="one" value="one">AS One</li>
            <li><input type="checkbox" name="two" value="two">AS Two</li>
            <li><input type="checkbox" name="user_roles" value="user_roles">Users &amp; Roles <!-- SHOULD CHECK HERE -->
                <ul>
                    <li><input type="checkbox" name="user_role" value="add">Add</li>
                    <li><input type="checkbox" name="user_role" value="delete">Delete</li> <!-- CHECK HERE -->
                </ul>
            </li>
        </ul>
    </li>

    <li><input type="checkbox" name="rl_module" value="yes">RL Module</li>

    <li><input type="checkbox" name="rl_module" value="yes">Accounting
        <ul>
            <li><input type="checkbox" name="vat" value="yes">VAT</li>
            <li><input type="checkbox" name="bank_account" value="yes">Banking
                <ul>
                    <li><input type="checkbox" name="view" value="yes">View</li>
                    <li><input type="checkbox" name="crud" value="yes">CRUD</li>
                </ul>
            </li>
        </ul>
    </li>

</ul>

And the corresponding javascript:

$('input[type=checkbox]').click(function(){

    // if is checked
    if($(this).is(':checked')){

        // check all children
        $(this).parent().find('li input[type=checkbox]').prop('checked', true);

        // check all parents
        $(this).parent().prev().prop('checked', true);

    } else {

        // uncheck all children
        $(this).parent().find('li input[type=checkbox]').prop('checked', false);

    }

});
Sumurai8
  • 20,333
  • 11
  • 66
  • 100
Pierce McGeough
  • 3,016
  • 8
  • 43
  • 65
  • So if I understand correctly, you want to reverse what it does right now. You want it to check all the parent boxes upon checking a child box. – Zhouster Jul 22 '14 at 16:06
  • Yes so it is like a privileges setting. You need the parent to be active but not the sibling – Pierce McGeough Jul 22 '14 at 16:09
  • Should the children still get checked, or should that functionality be removed? – j08691 Jul 22 '14 at 16:10
  • If you click Users & Roles. All the children should be checked/unchecked. The Accounting should be checked too. – Pierce McGeough Jul 22 '14 at 16:22
  • When you uncheck a child, should it uncheck its parents too? And if it does, should it uncheck the parent only if it's the last sibling checked, or uncheck all its siblings and parents automatically? Basically, is it okay if Account Settings is checked with none of its children checked, or it should only stay checked if at least one of its children are still checked? – Pluto Jul 22 '14 at 16:50
  • If all children are unchecked then the direct parent should be unchecked too. Account Settings will check all children but if you uncheck from the lowest child back up then Account settings should uncheck when no children are selected. – Pierce McGeough Jul 23 '14 at 08:29

5 Answers5

13

It looks like you want something like this

$('input[type=checkbox]').click(function(){
    if(this.checked){ // if checked - check all parent checkboxes
        $(this).parents('li').children('input[type=checkbox]').prop('checked',true);
    }
    // children checkboxes depend on current checkbox
    $(this).parent().find('input[type=checkbox]').prop('checked',this.checked); 
});

FIDDLE

If you want to check up and down hierarchy - you can do it like this

$('input[type=checkbox]').click(function(){
    // children checkboxes depend on current checkbox
    $(this).next().find('input[type=checkbox]').prop('checked',this.checked);
    // go up the hierarchy - and check/uncheck depending on number of children checked/unchecked
    $(this).parents('ul').prev('input[type=checkbox]').prop('checked',function(){
        return $(this).next().find(':checked').length;
    });
});

FIDDLE

wirey00
  • 33,517
  • 7
  • 54
  • 65
  • 2
    This is the most concise solution that functions entirely correctly. – Pluto Jul 22 '14 at 16:30
  • If I check user and roles, then uncheck it, account settings still stays checked. – j08691 Jul 22 '14 at 16:31
  • @j08691 It's not supposed to uncheck parents because then it would also have to check for siblings. "If you click Users & Roles. All the children should be checked/unchecked. The Accounting should be checked too." Notice how he doesn't say Accounting should be checked _and unchecked_ too. – Pluto Jul 22 '14 at 16:33
  • I don't think the OP thought it through completely. I'm guess he needs a siblings check. – j08691 Jul 22 '14 at 16:35
  • Why would you want a child to uncheck the parent when unchecking the parent already does that and the rest of the children? – Pluto Jul 22 '14 at 16:36
  • If I check and uncheck user and roles, why would I want account setting to remain checked? – j08691 Jul 22 '14 at 16:39
  • because you may also have AS One, or AS Two, checked, why would you want those to be forced unchecked? – Lochemage Jul 22 '14 at 16:41
  • Having AS One or Two isn't what I asked. – j08691 Jul 22 '14 at 16:43
  • Because otherwise there is no way to only have "Account Setting" checked. – Pluto Jul 22 '14 at 16:45
  • This is a great answer but how does it fare when there are a lot of checkboxes? Say 400-500 checkboxes? – Howard Apr 27 '18 at 19:21
6

This should do it:

$('input[type=checkbox]').click(function () {
    $(this).parent().find('li input[type=checkbox]').prop('checked', $(this).is(':checked'));
    var sibs = false;
    $(this).closest('ul').children('li').each(function () {
        if($('input[type=checkbox]', this).is(':checked')) sibs=true;
    })
    $(this).parents('ul').prev().prop('checked', sibs);
});

jsFiddle example

Latest update handles up and down the hierarchy, and siblings.

j08691
  • 204,283
  • 31
  • 260
  • 272
  • This version does not uncheck children properly, it unchecks all parents but not the children. – Lochemage Jul 22 '14 at 16:26
  • Now if I check the top level parent item, it checks all children – Lochemage Jul 22 '14 at 16:27
  • I updated my answer based on the OP's recent changes, yet there still seems to be some fringe cases that I'm not certain how the OP would want to handle. – j08691 Jul 22 '14 at 16:28
  • @Lochemage - "If you click Users & Roles. All the children should be checked/unchecked." – j08691 Jul 22 '14 at 16:28
  • @j08691 now when you check "Banking" then uncheck "View" the results are incorrect – wirey00 Jul 22 '14 at 16:29
  • Ah I see, there are still a few unusual behaviors, but I suppose if it is what the OP wants... – Lochemage Jul 22 '14 at 16:30
  • 1
    Now you've introduced a bug where if you check Accounting, then uncheck View and CRUD, it awkwardly leaves VAT checked by itself. You'll need to check siblings on every level until it's reached `#tree`. – Pluto Jul 22 '14 at 16:57
  • 3
    Something like this is how you'd have to handle unchecking parents while looking at siblings at each level: http://jsfiddle.net/Z82FR/3/ – Pluto Jul 22 '14 at 17:36
2

Just use jquery.parents(). It is somewhat similar to find() except it searches all parents. Something like this might be close to what you are looking for:

$(this).parents('li').each(function() {
  $(this).children('input').prop('checked', true);
});

See http://api.jquery.com/parents/ for more information.

EDIT: Alright, here is a solution that works:

http://jsfiddle.net/3y3Pb/12/

EDIT2: And a more streamlined solution here:

http://jsfiddle.net/3y3Pb/14/

Lochemage
  • 3,974
  • 11
  • 11
  • Yes, I know. Which is why I mentioned it might be 'close' to what you are looking for, because I realize that exactly that won't work, sorry. – Lochemage Jul 22 '14 at 16:15
  • Modify it to `$(this).parents('li').children('input[type="checkbox"]').prop('checked', true);` like this: http://jsfiddle.net/3y3Pb/13/ – Pluto Jul 22 '14 at 16:18
  • 1
    @Lochemage Why use `.each()`? – Pluto Jul 22 '14 at 16:22
  • 1
    I suppose you don't need to, I usually prefer it though since it makes things easier to read and debug – Lochemage Jul 22 '14 at 16:23
0

Have a JSFiddle: http://jsfiddle.net/3y3Pb/16/

I would recommend adding a parent attribute to the checkboxes. This parent attribute will reference the parent checkbox's id so that you don't have to worry about your structure changing:

$('input type=[checkbox]').change(function () {
    $('#' + $(this).attr('parent')).prop('checked', this.checked);
});

Ex:

<input type="checkbox" name="account_settings" value="yes" id="as">Account Settings
    <ul>
        <li><input type="checkbox" name="one" value="one" parent="as" id="one">AS One</li>
deckeresq
  • 332
  • 2
  • 14
0

You can use prevAll() also I have the same issue. In my case there are multiple checkboxes in li with labels, and each checkbox above target have class parent (generated in js)

$(this).parents().prevAll('input:checkbox.parent').each(function () {
                       $(this).attr('checked', true);
                   });
Evgeny Gil
  • 317
  • 5
  • 17