Riples Riples - 5 months ago 33
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

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/

Comments