16

I am using the accordion directive from http://angular-ui.github.com/bootstrap/ and I need to have more control over when the accordions open and close.

To be more precise I need a button inside the accordion-group that will close its parent accordion and open the next one (so basically mimic what clicking the next header would do if close-others was set to true). I also need to do some validation before I can allow an accordion to be closed and the next one to be opened, and I also need to wire this up to click events on the accordion headers.

I am pretty new to angular and we're currently rewriting an application from Backbone+JQuery to Angular. In the Backbone-version we were using Twitter Bootstrap accordions and we were opening and closing them using JQuery. While we can still keep doing this I would rather get rid of JQuery DOM manipulation completely so I am looking for a pure angular solution to this.

What I've tried to do in terms of validation is

<accordion-group ng-click="close($event)">

and in my controller

    event.preventDefault();
    event.stopPropagation();

This obviously did not work as the DOM element is replaced by the directive and the click-handler is never added. I've been going over the source code (and found a few very nice undocumented features) but I'm at a loss over where to even begin solving this specific challenge. I was considering forking angular-ui and try to add this functionality to the accordion directive but if I can achieve this without modifying the directive that would be a lot nicer.

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
ivarni
  • 17,658
  • 17
  • 76
  • 92

2 Answers2

25

There is the is-open attribute on the accordion-group which points to a bindable expression. By using this expression you can control accordion items programatically, ex.:

<div ng-controller="AccordionDemoCtrl">
  <accordion>
    <accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
      {{group.content}}
    </accordion-group>    
  </accordion>
  <button class="btn" ng-click="groups[0].open = !groups[0].open">Toggle first open</button>
  <button class="btn" ng-click="groups[1].open = !groups[1].open">Toggle second open</button>
</div>

and the working plunk here: http://plnkr.co/edit/DepnVH?p=preview

pkozlowski.opensource
  • 117,202
  • 60
  • 326
  • 286
  • Thanks, that's got my buttons working like I need them to. My other question though, is how I would go about doing some sort of validation before allowing the second accordion to be toggled (basically preventing it from being expanded). I've modified the plunk to work with the buttons: http://plnkr.co/edit/J5hnuA?p=preview But I was wondering if it is possible to prevent opening the second accordion by clicking on the header as well? – ivarni Mar 27 '13 at 10:26
  • (In my plunk you can't toggle accordion #2 by clicking the button unless the checkbox in #1 is ticked, but you can still open #2 by clicking the header) – ivarni Mar 27 '13 at 10:27
  • It seems there's no way to do some validation before opening a new accordion since the click-handler in the template just says ng-click="isOpen = !isOpen" – ivarni Apr 02 '13 at 08:47
  • Why not use the `is-disabled` parameter as shown in the demo? http://angular-ui.github.io/bootstrap/#/accordion – Brad Orego Jul 09 '14 at 09:06
  • @bradorego It didn't exist at the time, it was added in 0.11.0 on 2014-05-01 – ivarni Nov 12 '14 at 09:35
5

For whoever the solution by @pkozlowski.opensource is not working (me for example) you could just force the component to accept the CSS that will close it (without transition that is).

The Theory: The angular directive gets expanded into standard HTML, div elements mainly, where the CSS styles give it the appearance of the accordion. The div with class .panel-collapse is the body of the accordion-group element. You can swap its second class from .in to .collapse along with a few other changes as seen below.

The Code:

$scope.toggleOpen = function(project) {

        var id = '<The ID of the accordion-group you want to close>';
        var elements = angular.element($document[0].querySelector('#'+id));
        var children = elements.children();

        for(var i = 0; i < children.length; i++) {

            var child = angular.element(children[i]);

            if(child.hasClass('panel-collapse')) {
                if(child.hasClass('in')) { // it is open
                    child.removeClass('in');
                    child.addClass('collapse');
                    child.css('height', '0px');
                } else { // it is closed
                    child.addClass('in');
                    child.removeClass('collapse');
                    child.css('height', 'auto');
                }

            }
        }
    };

As we are talking about Angular, it is very possible that you are generating the accordion through an ng-repeat tag. In this case you can also generate the id's for the elements like:

<accordion-group ng-repeat="user in users"
                 is-disabled="user.projects.length == 0"
                 id="USER{{user._id}}">

Given a Mongoose model User, notice that the id I am giving is not user._id but has 'USER' appended in front. This is because Mongoose might generate id's that start numerically and querySelector does not like that ;-) go figure!

Mike M
  • 4,879
  • 5
  • 38
  • 58