5

So, I have been trying to animate kind of like a Angular Accordion, but with no success. I figured it out with fixed heights, but not with dynamic. height: auto; does not work. :(

Maybe some of you have had a similar problem?

My code:

html:

  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-route.js"></script>
    <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body>
    <section ng-app="myApp">
    <div ng-controller="myCtrl as vm">
        <ul ng-init="vm.tab=1">
            <li ng-repeat="item in vm.data">
              <a href ng-click="vm.tab = item.thingy">{{item.name}}</a>
              <div ng-show="vm.tab === item.thingy">
                <img ng-src="{{item.img}}" width="50px"><br>
                <div class="longDiv">{{item.description}}</div>
              </div>
            </li>
        </ul>
    </div>
  </section>
</body>
</html>

js:

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

app.controller('myCtrl', ['$scope',
  function($scope) {
    var vm = this;

    vm.data = [{
      name: "First",
      title: "oneTitle",
      description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur.",
      year: "2013",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42735.jpg",
      thingy: 1
    }, {
      name: "third",
      title: "twoTitle",
      description: "Quisque pulvinar libero sed eros ornare",
      year: "2014",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/1/8519.jpg",
      thingy: 2
    }, {
      name: "Second",
      title: "threeTitle",
      description: "Cras accumsan ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur massa vitae tortor vehicula .",
      year: "2015",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/43326.jpg",
      thingy: 3
    }, {
      name: "fourth",
      title: "FourTitle",
      description: "Suspendisse ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur ipsum dolor sit amet, consectetur vitae mattis magna.",
      year: "2011",
      img: "http://static.hdw.eweb4.com/media/wp_400/1/5/42413.jpg",
  thingy: 4
    }];

  }
]);

Thanks in advance! Plnkr here

5
  • 1
    You could use UI Bootstrap for this, which are bootstrap components written in pure AngularJs so you can easily customize. Check angular-ui.github.io/bootstrap , the first component is an accordion. Commented Jun 24, 2015 at 12:22
  • Small clarification: is your requirement to set all the collapsible panels to the same height? Commented Jun 24, 2015 at 12:25
  • 1
    No, I want panels to slide down to show the content. Only the width is set, height might change. Commented Jun 24, 2015 at 12:27
  • Slide down and slide up when clicked? Commented Jun 24, 2015 at 12:34
  • Yes, basically animate height from 0 to auto for active element. Commented Jun 24, 2015 at 12:36

5 Answers 5

7

I've solved this problem by creating a "collapse" directive:

ngModule.directive('collapse', [function () {
    return {
        restrict: 'A',

        link: function ($scope, ngElement, attributes) {
            var element = ngElement[0];

            $scope.$watch(attributes.collapse, function (collapse) {
                var newHeight = collapse ? 0 : getElementAutoHeight();

                element.style.height = newHeight +"px";
                ngElement.toggleClass('collapsed', collapse);
            });

            function getElementAutoHeight() {
                var currentHeight = getElementCurrentHeight();

                element.style.height = 'auto';
                var autoHeight = getElementCurrentHeight();

                element.style.height = currentHeight +"px";
                getElementCurrentHeight(); // Force the browser to recalc height after moving it back to normal

                return autoHeight;
            }

            function getElementCurrentHeight() {
                return element.offsetHeight
            }
        }
    };
}]);

To use the directive, you just throw it on your element and set it to the scope variable that says whether or not to collapse it:

<div collapse="isCollapsed">
    This will be collapsed
</div>

Now you just need to apply a CSS transition on the height and it will smoothly collapse & expand. Since this directive also adds a class, you can apply other transitions like opacity.

Here's a Codepen with an example: http://codepen.io/KaidenR/pen/GoRJLx

Sign up to request clarification or add additional context in comments.

Comments

3

Modified @kaiden answer, if using angular directive can use

require('./').directive('collapse', collapseDirective);

/**
* @ngInject
*/
function collapseDirective() {

   return {
    restrict: 'A',
    link: link
   };

   function link($scope, ngElement, attributes) {
     var element = ngElement[0];

     // set the height as a data attr so we can use it again later
     element.dataset.heightOld = angular.element(element).prop('offsetHeight');

     $scope.$watch(attributes.collapse, function (collapse) {
       var newHeight = !collapse ? 0 : element.dataset.heightOld;
       element.style.height = newHeight + 'px';
       ngElement.toggleClass('collapsed', collapse);
     }); 
   }
 }

As the link function then just add CSS transitions for height. This sets a data attribute to store the height then uses it when toggling. In my application I used some markup like

<ion-item class="item-accordion" collapse="vm.isActive($index)">

Note this is inside an ng-repeat and calls a function on view model. This is also an ionic project hence the ion item tag.

Comments

1

You can try setting max-height to 0 and then remove the style/class as suggested here:

http://css3.bradshawenterprises.com/animating_height/

1 Comment

Thank you, but hover does not work for me. I need it to show on click just like in my example :(
1

So, after long hours of trying to figure out what is the easiest and shortest way to make jQuery accordion with angular content, I figured one easy, but messy way.

Basically I made a new variable in my $scope- accordion, and put in there all accordion html:

vm.data = [{
  name     : "First",
  accordion: ["<h3>Title1</h3><div>Content 1</div><h3>Title2</h3><div>Content 1</div>"]};

For me this works, because now I can use this variable in ng-repeat:

<div ng-repeat="item in vm.data>
    <div ng-repeat="acc in item.accordion" ng-bind-html="acc | normalHtml"></div>

And as long there is only one string in this variable, I get one accordion div. After that I called accordion function inside a setTimout function and everything works fine.

BUT this is messy and kind of "with a hammer where I could've used a feather" situation.

So, for now this is fine for me, but I am still looking for a prettier solution.

Thank you all for helping me out. If some of you have any idea how to make this better, I welcome every answer :)

Comments

0

Ok I think I found the holy grail. At least for my uses :)

I was having trouble using nested collapsed elements with the solution from @Kaiden and @Gareth Fuller, as any outer collapse element would keep the original height that it got before any inner collapse elements were collapsed. So I got inspired from @Icycool's suggestion and changed to animate on max-height, and keep height set to 'auto', which fixes that.

You can run a working demo below.

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

testModule.controller('testController', function($scope) {
  $scope.isCollapsed = false;

  $scope.toggle = function() {
    $scope.isCollapsed = !$scope.isCollapsed;
  };
});

testModule.directive('collapse', [function() {
  return {
    restrict: 'A',

    link: function link($scope, $element, $attrs) {
      var element = $element[0];
      $element.addClass('collapse');
      const originalHeight = $element.prop('offsetHeight');

      $scope.$watch($attrs.collapse, function(collapse) {
        if (collapse) {
          element.style.maxHeight = '0px';
        } else {
          element.style.maxHeight = originalHeight + 'px';
        }
        $element.toggleClass('sh-collapsed', collapse);
      });
    }
  };
}]);
.collapse {
  height: 'auto';
  overflow: hidden;
  transition: max-height 0.3s;
  background: lightgreen;
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.4/angular.min.js"></script>
<div ng-app="testModule" ng-controller="testController">
  <button type="button" ng-click="toggle()">Toggle</button> Collapsed: {{isCollapsed}}
  <ul collapse="isCollapsed">
    <li>Line One</li>
    <li>Line Two</li>
    <li>Line Three</li>
    <li>Line Four</li>
    <li>Line Five</li>
  </ul>
</div>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.