WABBIT0111
WABBIT0111

Reputation: 2313

angular ui-bootstrap accordion how to achieve customized behavior: default all close, when clicking one accordion-group heading, open all others

I am working with ui-bootstrap accordion. I have an accordion inside an accordion. enter image description here

under the 1st accordion 'Category', I have another accordion, with 3 accordion- group. They are closed by default. I would like this behavior: when you click an accordion group title (say 'fruit'), if all the accordion groups are closed, then it will open them all... but if any groups is open( say if any of the 'fruit', 'meat', and vegetable' is open when user is clicking), then when you click, it will toggle the clicked accordion-group. you can check out the plunker here:

http://plnkr.co/edit/UcETfOVh8RwMX40mOCVv?p=preview

my html and angular code is follow:

<!DOCTYPE html>
<html ng-app="plunker">

  <body ng-controller="AccordionDemoCtrl">
    <accordion >
      <accordion-group>
        <accordion-heading> Category</accordion-heading>

        <accordion ng-repeat="(category, items) in categories" close-others="oneAtATime" is-open="true">
          <accordion-group >
            <accordion-heading ><div>{{category}}</div></accordion-heading>

            <div ng-repeat="item in items.data">{{item}}</div>
          </accordion-group>
        </accordion>

      </accordion-group>
    </accordion>

  </body>

</html>


angular.module('plunker', ['ui.bootstrap']);
function AccordionDemoCtrl($scope) {
  $scope.categories = {
    fruit: {
      data: {
        apple: 3,
        orange: 5,
       lemon: 6
      },
      toggled: false

    },
    vegetable: {
      data: {
         lettuce: 1,
        broccoli: 5, 
        spinach: 4
      },
      toggled: false

    },
    meat: {
      data: {
        chicken: 3,
        beef: 6,
        lamb: 8
      },
      toggled: false

    }
  };
}

How can I achieve such behavior? I have work progress in another plunker: http://plnkr.co/edit/9eTfpn81g57Dk4FtVSlA?p=preview I've explored with 'is-open' attribute in accordion, I've tried to refered to the $parent, not producing the behavior i want.

Upvotes: 1

Views: 2083

Answers (2)

AWolf
AWolf

Reputation: 8970

As mentioned by bobleujr is-open should work. But the code is a bit tricky and I'm not sure if there is an easier solution.

The problem is that the ng-click where I'm adding my 'toggleAll' check is called after toggeling the clicked accordion. So I have to track the previous accordion state to be sure that we have to toggle all accordions. I'm doing this by looping over all accodions states and count if it is open.

Then I can check later if previousCount == 0 and currentCount==1 we need to open all.

I don't like doing two for loops for the behaviour but I think that's the only way to do it.

If it wouldn't toggle the state with ng-click it would be easier to do. But I don't know how. I've tried to disable the is-open toggle of the accordion directive but that wasn't working.

Decorating of the accordion directive could help then you could add the check before toggeling. But that's probably not that easy.

Please have a look at the demo below or here in the updated plunkr.

// Code goes here
angular.module('plunker', ['ui.bootstrap'])
.controller('AccordionDemoCtrl', AccordionDemoCtrl);

function AccordionDemoCtrl($scope) {
  var prevOpenCount = 0;
  
  $scope.toggle = function(index) {
        var openCount = 0,
            openAll = false;
        
        // check how many accordions are open
        for( var i=0; i < $scope.openState.length; i++ ){
          if ($scope.openState[i] === true) {
              openCount++;
          }
        }
        
        if ( openCount === 1 && prevOpenCount === 0) { // open all
          for( var i=0; i < $scope.openState.length; i++ ){
            $scope.openState[i] = true;
          }
        }
       
        //console.log(index, $scope.openState, openCount, prevOpenCount);
        prevOpenCount = openCount;
      };
      
  $scope.categories = {
    fruit: {
      data: {
        apple: 3,
        orange: 5,
       lemon: 6
      },
      toggled: false
      
    },
    vegetable: {
      data: {
         lettuce: 1,
        broccoli: 5, 
        spinach: 4
      },
      toggled: false
     
    },
    meat: {
      data: {
        chicken: 3,
        beef: 6,
        lamb: 8
      },
      toggled: false
      
    }
  };
  

  $scope.openState = new Array(Object.keys($scope.categories).length); 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.js"></script>
    <!--<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.9.0.js"></script>-->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular-animate.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.3/ui-bootstrap-tpls.js"></script>
    
    <!--<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    --><script src="script.js"></script>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">

<div ng-app="plunker" ng-controller="AccordionDemoCtrl">
    
    <accordion>
      <accordion-group>
        <accordion-heading> Category</accordion-heading>
        
        <accordion ng-repeat="(category, items) in categories" close-others="oneAtATime">
          <accordion-group ng-click="toggle($index, $event)" is-open="openState[$index]">
            
            <accordion-heading ><div><!--{{openState}}-->{{category}}</div></accordion-heading>
            
            <div ng-repeat="item in items.data">{{item}}</div>
          </accordion-group>
        </accordion>
        
      </accordion-group>
    </accordion>
  </div>

Upvotes: 3

bobleujr
bobleujr

Reputation: 1176

WABBIT0111

you may want to use the attribute is-open=ctrlVar.

This way you will be able to set it with a ng-click=action to change ctrlVar state and then toggle them back to original position.

Upvotes: 1

Related Questions