Vijay Ponduru
Vijay Ponduru

Reputation: 31

How to have a checkbox in an ng-grid grouping

Is it possible to have a check box in an ng-grid grouping header? When that grouping check box is clicked, all the checkboxes of the rows under that particular group should be selected. The other group rows should remain un-selected.

Right now, when I have my gridOptions as follows, I see checkboxes for all rows, and in the table header. When a column header is dragged to use it for grouping, the grouped rows-header do not have checkboxes. Can anybody help?

$scope.gridOptions = {
    data: 'myData',
    showGroupPanel: true,
    showSelectionCheckbox: true,
    jqueryUITheme: true,
    enableCellSelection: true,
    enableRowSelection: true,
    enableCellEdit: false,
    pinSelectionCheckbox: false,
    selectWithCheckboxOnly: true
};

Upvotes: 3

Views: 1662

Answers (2)

daemone
daemone

Reputation: 1191

I found this question while desperately seeking an answer to the same problem, and while Eric's answer pointed me in the right direction, it wasn't working as expected*.

Here's what I came to in the end, after a couple of days of playing around with the contents of the ngRow and ngAggregate objects.

My aggregateTemplate is pretty similar to Eric's, but there are a few differences. First, I've added the expand/collapse icon thing back in (span class="{{row.aggClass()}}></span>"). I've also moved the ng-click="expandGroupChildren($event, row)" to the containing div, and added ng-click="$event.stopPropagation()" to the checkbox. This means that clicking anywhere but the checkbox will expand/collapse the group. Finally, I've renamed some functions.

var aggregateTemplate = '<div ng-init="initAggregateGroup(row)" ng-style="rowStyle(row)" style="top: 0; height: 48px; left: 0; cursor:pointer" class="ngAggregate" ng-click="expandGroupChildren($event, row)">' +
    '<span class="{{row.aggClass()}}"></span>' +
    '<input class="ngSelectionHeader" type="checkbox" ng-show="multiSelect" ng-checked="row.status.isChecked" ng-model="row.status.isChecked" ng-change="setGroupSelection(row)" ng-click="$event.stopPropagation()" />' +
    '<span class="ngAggregateText">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})</span>' +
'</div>';

But the function code is where I really diverge.

First, this just doesn't work for me:

$scope.gridOptions.selectItem(entry.rowIndex, group.status.isChecked)

Instead, in this version we call this:

row.selectionProvider.setSelection (row, group.status.isChecked)

Since it takes the row itself instead of an index, it just works, no matter how tangled the indices might get.

This version also works with nested groups. When you group by, for instance, City > Age, and you get a group Minnesota containing a group 53, clicking on the group header for 53 will select all 53-year-olds who live in Minesota, but not 53-year-olds in other cities or Minnesotans of other ages. Clicking the group header for Minnesota, on the other hand, will select everyone in Minnesota, and the group header checkboxes for every sub-group. Likewise, if we only have 53-year-olds in the the Minnesota group, then clicking the 53 checkbox will also tick the Minnesota checkbox.

And that brings me to the final change. With a watcher per group (I don't generally like watchers and try to avoid them, but sometimes they're a necessary evil), we keep track of the selection within each group and automatically tick the box in the group header when every row is selected. Just like the select-all checkbox at the top of the grid.

So here's the code:

angular.extend ($scope, {
    initAggregateGroup  : initAggregateGroup,
    expandGroupChildren : expandGroupChildren,
    setGroupSelection   : setGroupSelection
});

function initAggregateGroup (group) {
    group.status = {
        isChecked: getGroupSelection (group)
    };
    $scope.$watch (
        function () {
            return getGroupSelection (group)
        },
        function (isSelected) {
            setGroupSelection (group, isSelected);
        }
    )
}

function expandGroupChildren ($event, group) {
    $event.stopPropagation ();
    $event.preventDefault ();
    group.toggleExpand ();
}

function setGroupSelection (group, isSelected) {
    // Sets the field when called by the watcher (in which case the field needs to be updated)
    // or during recursion (in which case the objects inside aggChildren don't have it set at all)
    if (_.isBoolean (isSelected)) {
        group.status = { isChecked : isSelected }
    }
    // Protects against infinite digest loops caused by the watcher above
    if (group.status.isChecked === getGroupSelection (group)) { return }

    _.forEach (group.children, function (row) {
        // children: ngRow objects that represent actual data rows
        row.selectionProvider.setSelection (row, group.status.isChecked);
    });
    _.forEach (group.aggChildren, function (subGroup) {
        // aggChildren: ngAggregate objects that represent groups
        setGroupSelection (subGroup, group.status.isChecked);
    });
}

function getGroupSelection (group) {
    if (group.children.length > 0) {
        return _.every (group.children, 'selected');
    }
    if (group.aggChildren.length > 0) {
        return _.every (group.aggChildren, getGroupSelection);
    }
    return false;
}

*Clicking the checkbox in the aggregateTemplate would select a seemingly random collection of rows from all across the grid (seemingly random, because it was consistent throughout separate sessions, for the same data).

I think the problem (at least for me in ngGrid 2.0.12) was that ngGrid wasn't properly mapping the rowIndex field to the right row in its model. I think this was because the rows were rearranged for the grouping as well as the sorting, and the internal mapping hadn't kept up.

Upvotes: 2

Eric Alter
Eric Alter

Reputation: 21

In order to make it, you have first to override the default aggregateTemplate :

$scope.gridOptions = {
  data: 'myData',
  showGroupPanel: true,
  showSelectionCheckbox: true,
  jqueryUITheme: true,
  enableRowSelection: true,
  aggregateTemplate: "<div ng-init=\"initAggregateGroup(row)\" ng-style=\"rowStyle(row)\" style=\"top: 0px; height: 48px; left: 0px; cursor:pointer\" class=\"ngAggregate\">" +
"    <span class=\"ngAggregateText\" ng-click=\"expandGroupChilds($event, row)\">{{row.label CUSTOM_FILTERS}} ({{row.totalChildren()}} {{AggItemsLabel}})</span>" +
"<input class=\"ngSelectionHeader\" type=\"checkbox\" ng-show=\"multiSelect\" ng-checked=\"row.status.isChecked\" ng-model=\"row.status.isChecked\" ng-change=\"toggleGroupSelectedChilds(row)\" /></div>"  
};

Then you can add those functions in your controller:

$scope.expandAll = function($event){
   $scope.isAllCollapsed = !$scope.isAllCollapsed;
   _.each($scope.ngGridOptions.ngGrid.rowFactory.aggCache, function(row){
     row.toggleExpand();
   });
};

$scope.initAggregateGroup = function(group){
   group.status = {
      isChecked: false
   };
};

$scope.toggleGroupSelectedChilds = function(group){
  _.each(group.children, function(entry){
      $scope.ngGridOptions.selectItem(entry.rowIndex, group.status.isChecked);
  });
};

$scope.expandGroupChilds = function($event, group){
   $event.stopPropagation();
   $event.preventDefault();

   group.toggleExpand();
};

Upvotes: 2

Related Questions