23

I am trying to figure out a way to do an expand and collapse using angular js. I haven't been able to find an elegant way to do this without manipulating dom objects in the controller (which is not the angular way). Currently I have a nice way to do it for a one layer expand and collapse. However when I start nesting, things get complicated and don't work the way I want it to (expanding and collapsing multiple areas when they shouldn't be). The problem comes in by me not knowing how to send a unique identifier with an ng-click to only expand/collapse the right content. I should mention that these items are in an ng-repeat so I can necessarily customize parameters being sent.

I was able to use this jsfiddle to help me get a one layer expand and collapse to work. However this is a toggle way to do it and I want the user to be able to keep things expanded while expanding others. So what I did to fix this was use an array of and every time something is clicked the index of that clicked item would be added to the array and the class expanded. When the user clicked again the index was removed from the array and the area was collapsed.

Another way I found that you can do it is by using directives. But I can't really find any exampled to wrap my head around how directives work. I am not sure how to even start when it comes to writing directives.

My current set up is this:

function Dexspander($scope) {
    $scope.ExpandArray = [];

    //Push or pop necessary elements for tracking
    $scope.DespopulatArray = function (identifier, element) {
    if (_.indexOf($scope.ExpandArray, identifier + element) != -1) {
            $scope.ExpandArray.splice(_.indexOf($scope.ExpandArray, identifier + element), 1);
        } else {
            $scope.ExpandArray.push(identifier + element);
        }
    }

    //Change class of necessary elements
    $scope.Dexspand = function (identifier, element) {
        if (_.indexOf($scope.ExpandArray, identifier + element) != -1) {
            return "expand";
        } else {
            return "collapse";
        }
    }
}

<div class="user-header" ng-repeat="user in users">
    <h1 ng-click="DespopulatArray('user', $index)">Expand</h1>
</div>
<div class="user-content" ng:class="Dexspand('user', $index)">
    <div class="content">
        <div class="user-information">
            <div class="info-header">
                <h1 ng-click="DespopulatArray('info', $index)>Some Information</h1>
            </div>
            <div class="info-contents" ng:class="Dexspand('info', $index)">
                stuff stuff stuff stuff...
            </div>
        </div>
    </div>
</div>

The issue with this setup is that if I have to parent divs expanded and those both have something under them to expand, clicking the expand text will expand them in both areas as they are not unique.

dain
  • 6,183
  • 1
  • 34
  • 42
yaegerbomb
  • 1,126
  • 4
  • 22
  • 35
  • Could you put an example of this up on plunker? Then I might be able to help you with a directive. use this as a base: http://plnkr.co/edit/gist:3662656 – Andrew Joslin Sep 26 '12 at 16:15

6 Answers6

27

You can solve this fully in the html:

<div>
  <input ng-model=collapse type=checkbox>Title
  <div ng-show=collapse>
     Only shown when checkbox is clicked
  </div>
</div>

This also works well with ng-repeat since it will create a local scope for each member.

<table>
  <tbody ng-repeat='m in members'>
    <tr>
       <td><input type=checkbox ng-model=collapse></td>
       <td>{{m.title}}</td>
    </tr>
    <tr ng-show=collapse>
      <td> </td>
      <td>{{ m.content }}</td>
    </tr>
  </tbody>
</table>

Be aware that even though a repeat has its own scope, initially it will inherit the value from collapse from super scopes. This allows you to set the initial value in one place but it can be surprising.

You can of course restyle the checkbox. See http://jsfiddle.net/azD5m/5/

Updated fiddle: http://jsfiddle.net/azD5m/374/ Original fiddle used closing </input> tags to add the HTML text label instead of using <label> tags.

altShiftDev
  • 1,106
  • 7
  • 17
Peter Kriens
  • 14,454
  • 1
  • 32
  • 51
16

The problem comes in by me not knowing how to send a unique identifier with an ng-click to only expand/collapse the right content.

You can pass $event with ng-click (ng-dblclick, and ng- mouse events), then you can determine which element caused the event:

<a ng-click="doSomething($event)">do something</a>

Controller:

$scope.doSomething = function(ev) {
    var element = ev.srcElement ? ev.srcElement : ev.target;
    console.log(element, angular.element(element))
    ...
}

See also http://angular-ui.github.com/bootstrap/#/accordion

jox
  • 1,777
  • 19
  • 28
Mark Rajcok
  • 348,511
  • 112
  • 482
  • 482
7

See http://angular-ui.github.io/bootstrap/#/collapse

function CollapseDemoCtrl($scope) {
  $scope.isCollapsed = false;
}



<div ng-controller="CollapseDemoCtrl">
    <button class="btn" ng-click="isCollapsed = !isCollapsed">Toggle collapse</button>
    <hr>
    <div collapse="isCollapsed">
        <div class="well well-large">Some content</div> 
    </div>
</div>
3

I just wrote a simple zippy/collapsable using Angular using ng-show, ng-click and ng-init. Its implemented to one level but can be expanded to multiple levels easily.

Assign a boolean variable to ng-show and toggle it on click of header.

Check it out here enter image description here

Allzhere
  • 942
  • 1
  • 8
  • 19
2

Here a simple and easy solution on Angular JS using ng-repeat that might help.

var app = angular.module('myapp', []);

app.controller('MainCtrl', function($scope) {

  $scope.arr= [
    {name:"Head1",desc:"Head1Desc"},
    {name:"Head2",desc:"Head2Desc"},
    {name:"Head3",desc:"Head3Desc"},
    {name:"Head4",desc:"Head4Desc"}
    ];
    
    $scope.collapseIt = function(id){
      $scope.collapseId = ($scope.collapseId==id)?-1:id;
    }
});
/* Put your css in here */
li {
  list-style:none;
  padding:5px;
  color:red;
}
div{
  padding:10px;
  background:#ddd;
}
<!DOCTYPE html>
<html ng-app="myapp">
  <head>
    <meta charset="utf-8" />
    <title>AngularJS Simple Collapse</title>
    <script data-require="angular.js@1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
</head>
<body ng-controller="MainCtrl">
<ul>
  <li ng-repeat='ret in arr track by $index'>
    <div ng-click="collapseIt($index)">{{ret.name}}</div>
    <div ng-if="collapseId==$index">
      {{ret.desc}}
    </div>
  </li>
</ul>
</body>
</html>

This should fulfill your requirements. Here is a working code.

Plunkr Link http://plnkr.co/edit/n5DZxluYHi8FI3OmzFq2?p=preview

Github: https://github.com/deepakkoirala/SimpleAngularCollapse

ideeps
  • 289
  • 2
  • 14
0

In html

button ng-click="myMethod()">Videos</button>

In angular

 $scope.myMethod = function () {
         $(".collapse").collapse('hide');    //if you want to hide
         $(".collapse").collapse('toggle');  //if you want toggle
         $(".collapse").collapse('show');    //if you want to show
}
shahzain ali
  • 1,353
  • 1
  • 15
  • 29