Riples Riples - 1 year ago 78
AngularJS Question

Group panels inside a panel using AngularJS and Bootstrap

I'm unable to find an answer to this, or I'm not looking in the right place, but I'm trying to find a way where I can group a number of elements (panels) inside one large panel using both AngularJS and Bootstrap.

So for example, I have around 60 or so mini panels that represent customer contact forms. All of these forms sit inside one large panel with an

ng-repeat
. I am wanting to somehow group my contacts by different criteria (e.g By State code or by Company Type, etc.) from a dropdown control.

I would like all contacts to be shown in the one panel but segregated by a
.page-header
(a similar setup to the example below):

enter image description here

Has anyone seen any examples of this anywhere, or have any details on how this could be achieved?

Answer Source

You can make use of array group-by using Array#reduce, ng-repeat-start and ng-repeat-end:

HTML (I used bootstrap's panel here for illustrative purpose):

<body ng-app="MyApp">
  <div ng-controller="MyCtrl">
    <div class="panel panel-default">
      <div class="panel-heading" ng-repeat-start="(group, values) in groupedData">
        {{ group }}
      </div>
      <div class="panel-body" ng-repeat-end ng-repeat="value in values">
         {{ value.name }}
      </div>
    </div>

  </div>
</body>

JavaScript:

angular.module('MyApp', []).controller('MyCtrl', ['$scope', function($scope){

  var rawData = [{
    "name": "A1",
    "group": "A"  // For simplicity, my grouping criteria is called 'group'
  },{
    "name": "A2",
    "group": "A"
  },{
    "name": "A3",
    "group": "A"
  },{
    "name": "B1",
    "group": "B"
  },{
    "name": "C1",
    "group": "C"
  },{
    "name": "C2",
    "group": "C"
  }];

  // Perform a Group-by on the rawData: {"A": [{...}], ...} using Array#reduce
  $scope.groupedData = rawData.reduce(function(accumulator, value){
    if(!accumulator[value.group]) accumulator[value.group] = [];
    accumulator[value.group].push(value);
    return accumulator;
  }, {});

}]);

JSBin Example: http://jsbin.com/gigerilope/edit?html,js,output

Edit: Answering your extended question:

To trigger this grouping upon some button click, we first add a button and bind a ng-click handler:

<button class="btn btn-default" ng-click="performGrouping()">
  Perform Grouping
</button>

On the controller, instead of assigning the grouped data immediately, we wrap it in the handling function that would be called on the ng-click:

  $scope.groupedData = null;
  $scope.performGrouping = function() {
    $scope.groupedData = rawData.reduce(function(accumulator, value) {
      if (!accumulator[value.group]) accumulator[value.group] = [];
      accumulator[value.group].push(value);
      return accumulator;
    }, {});
  };

Now we want to show a "No Grouping" before clicking on the button, so we add a <div> panel to tell the story:

<div class="panel panel-default">
  <div class="panel-body">
    No Grouping Yet
  </div>
</div>

At this point, both panels could be seen - we need to show one at a time. Truth-checking $scope.groupedData would be a good idea:

Results Panel:

<div class="panel panel-default" ng-show="groupedData">

"No Grouping" panel:

<div class="panel panel-default" ng-hide="groupedData">

However, in the case where rawData is an empty array, groupedData would be an empty object {}, which resolves to true. We can do a keys-check to check how many keys groupedData has. To do this, we have to add a function to return the keys:

$scope.keys = function(obj) {
  return obj && Object.keys(obj);
};

And then update the template HTML again:

Results Panel:

<div class="panel panel-default" ng-show="keys(groupedData).length">

"No Grouping" panel:

<div class="panel panel-default" ng-hide="keys(groupedData).length">

And we should be good for now.

JSFiddle to the solution: https://jsfiddle.net/kazenorin/Ler7p6fe/