0

I have several dynamic checkbox groups (ul>li>checkbox>ul>li>checkbox) and need to change the check state of the parent to unchecked if children are unchecked or to indeterminate if only one child is checked. I have working unchecking the children if the parent is unchecked but need to tie each group together but separate from the other groups. The child is unchecking the top level parent item in the list of groups.

HTML

<div class="form-group">
   <ul>
      <li ng-repeat="state in filtersController.filtersModel().states">
         <input type="checkbox" name="{{state.displayName}}" id="{{state.displayName}}" class="parent" ng-checked="true" ng-model="state.selected" ng-change="filtersController.filterChanged(state)">
         <label for="{{state.displayName}}">{{state.displayName}}</label>
             <ul>
                 <li ng-repeat="county in filtersController.filtersModel().counties">
                  <div ng-show="displayCounties(county, state)">
                     <input type="checkbox" name="{{county.displayName}}" id="{{county.displayName}}" class="child" ng-checked="true" ng-model="county.selected" ng-change="filtersController.filterChanged(county)">
                     <label for="{{county.displayName}}">{{county.displayName}}</label>
                  </div>
               </li>
            </ul>
         </li>
      </ul>
   </div>

JS

this.getChecked = function () {
    $('input[type=checkbox]').change(function () {
    // parent unchecked will uncheck children
    $(this).parent().find('li input[type=checkbox]').prop('checked', $(this).is(':checked'));
    $(this).closest('ul').find('ul li input[type=checkbox][class=parent]').first().prop('indeterminate', $(this).is(':checked'));
});

$('li :checkbox').on('click', function () {
    var $chk = $(this),
    $li = $chk.closest('li'),
    $ul, $parent;
    if ($li.has('ul')) {
        $li.find(':checkbox').not(this).prop('checked', this.checked);
    }
do {
    $ul = $li.parent();
    $parent = $ul.siblings(':checkbox');
    if ($chk.is(':checked')) {
        $parent.prop('checked', $ul.has(':checkbox:not(:checked)').length === 0);
    } else {
        $parent.prop('checked', false);
    }
        $chk = $parent;
        $li = $chk.closest('li');
    } while ($ul.is(':not(.someclass)'));
    });
};

I am using AngularJS for my data structure and jQuery for to interact with the checkboxes.

bcedergren
  • 85
  • 1
  • 8
  • This is a really bad approach using jQuery on top of angular like that. Should be using your data model in controller to do this. Will be less code and no need whatsoever to search the dom. Also far simpler for testing. Suggested reading [thinking-in-angularjs-if-i-have-a-jquery-background](http://stackoverflow.com/questions/14994391/thinking-in-angularjs-if-i-have-a-jquery-background) – charlietfl Jan 18 '16 at 21:15
  • So, if I do not use jQuery, which I am completely in agreement with, and the data object is coming in through a service outside of the controller, is there a good way to reference that data object so that I am able to interact with the checked state? And, how would I best go about checking the children state against the parent state and vice-versa? – bcedergren Jan 18 '16 at 21:39

1 Answers1

0

Once you start thinking of using the data model first you will see how much easier this is to do with angular only.

Simplified HTML

<!-- note ng-checked and ng-model don't go together -->
<input type="checkbox" ng-model="state.selected" 
       ng-change="filtersController.parentChange('state',state)">

    <ul>
      <li ng-repeat="county in filtersController.counties">
        <input type="checkbox" ng-model="county.selected" 
               ng-change="filtersController.childChanged('counties')">
        <label for="{{county.displayName}}">{{county.displayName}}</label>

      </li>
    </ul>

Controller (untested)

var vm = this;
vm.parentChange = parentChange;
vm.childChanged = childChanged;

function parentChange(type, parent) {
  var children
  if (type === 'state') {
    children = vm.counties;
  }
  // when parent changes ... all children get same state as parent
  children.forEach(function(item) {
    item.selected = parent.selected
  });
}

function childChanged(type) {
  var parent, childGroup, allChecked;

  if (type === 'counties') {
    parent = vm.state;
    childGroup = vm.counties;
    // when child changes see if any are left unchecked in group
    allChecked = !childGroup.filter(function(item) {
      return !item.selected
    }).length; 
    // parent dependent on all children checked or not
    parent.selected = allChecked;
  }
}

End result is nothing in controller cares about the DOM, the DOM is driven by the data model in controller

charlietfl
  • 170,828
  • 13
  • 121
  • 150