Gérer les événements open / collapse de L'accordéon dans Angular

Si j'ai ce code:

<accordion-group heading="{{group.title}}" ng-repeat="group in groups">
      {{group.content}}
</accordion-group>

En utilisant AngularJS, angular-ui et Twitter Bootstrap, est-il possible de faire appel à l'accordéon une fois ouvert? Je sais que je ne peux pas simplement ajouter ng-click, car cela est déjà utilisé après qu'il soit "compilé" en HTML pour l'ouverture / l'effondrement du groupe.

39
demandé sur Michal 2013-03-26 20:17:04

7 réponses

Il y a l'attribut is-open sur le groupe accordéon qui pointe vers une expression bindable. Vous pouvez regarder cette expression et exécuter une logique lorsqu'un groupe d'accordéon donné est ouvert. En utilisant cette technique, vous changeriez votre balisage en:

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
   {{group.content}}
</accordion-group>

Afin que vous puissiez, dans le contrôleur, préparer une expression de montre souhaitée:

$scope.$watch('groups[0].open', function(isOpen){
    if (isOpen) {
      console.log('First group was opened'); 
    }    
  });

Bien que ce qui précède fonctionne, il peut être un peu lourd à utiliser dans la pratique, donc si vous sentez que cela pourrait être amélioré, ouvrez un problème dans https://github.com/angular-ui/bootstrap

24
répondu pkozlowski.opensource 2013-03-26 16:38:46

Les groupes Accordéon permettent également une directive accordéon-heading au lieu de la fournir en tant qu'attribut. Vous pouvez l'utiliser, puis enveloppez votre tête dans une autre balise avec un ng-click.

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
  <accordion-heading>
    <span ng-click="opened(group, $index)">{{group.content}}</span>
  </accordion-heading>
</accordion-group>

Exemple: http://plnkr.co/edit/B3LC1X?p=preview

32
répondu kjv 2016-12-02 15:39:51

Voici une solution basée sur pkozlowski.solution opensource .
Au lieu d'ajouter un $watch sur chaque élément de la collection, vous pouvez utiliser une propriété définie dynamiquement. Ici, vous pouvez lier le IsOpened la propriété de la le groupe de la est-ouvrir attribut.

<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.IsOpened">
   {{group.content}}
</accordion-group>

Ainsi, vous pouvez ajouter dynamiquement la IsOpened bien sur chaque élément de la collection dans le contrôleur :

$scope.groups.forEach(function(item) {
  var isOpened = false;
  Object.defineProperty(item, "IsOpened", {
    get: function() {
      return isOpened;
    },
    set: function(newValue) {
      isOpened = newValue;
      if (isOpened) {
        console.log(item); // do something...
      }
    }
  });
});

En utilisant Propriétés à la place de montres est mieux pour les performances.

29
répondu Khonsort 2016-12-02 16:02:05

, j'ai utilisé un tableau associatif pour créer une relation entre l'état ouvert et l'objet de modèle.

Le code HTML est:

  <div ng-controller="CaseController as controller">


                <accordion close-others="controller.model.closeOthers">
                    <accordion-group ng-repeat="topic in controller.model.topics track by topic.id" is-open="controller.model.opened[topic.id]">
                       <accordion-heading>
                          <h4 class="panel-title clearfix" ng-click="controller.expand(topic)">
                         <span class="pull-left">{{topic.title}}</span>
                         <span class="pull-right">Updated: {{topic.updatedDate}}</span>
                          </h4>                           
                       </accordion-heading>
                  <div class="panel-body">

                      <div class="btn-group margin-top-10">
                          <button type="button" class="btn btn-default" ng-click="controller.createComment(topic)">Add Comment<i class="fa fa-plus"></i></button>
                      </div>
                     <div class="btn-group margin-top-10">
                         <button type="button" class="btn btn-default" ng-click="controller.editTopic(topic)">Edit Topic<i class="fa fa-pencil-square-o"></i></button>
                     </div>
                      <h4>Topic Description</h4>
                      <p><strong>{{topic.description}}</strong></p>
                      <ul class="list-group">
                          <li class="list-group-item" ng-repeat="comment in topic.comments track by comment.id">
                              <h5>Comment by: {{comment.author}}<span class="pull-right">Updated: <span class="commentDate">{{comment.updatedDate}}</span> | <span class="commentTime">{{comment.updatedTime}}</span></span></h5>
                              <p>{{comment.comment}}</p>
                             <div class="btn-group">
                               <button type="button" class="btn btn-default btn-xs" ng-click="controller.editComment(topic, comment)">Edit <i class="fa fa-pencil-square-o"></i></button>
                               <button type="button" class="btn btn-default btn-xs" ng-click="controller.deleteComment(comment)">Delete <i class="fa fa-trash-o"></i></button>
                             </div>
                          </li>
                      </ul>
                  </div>

                    </accordion-group>
                </accordion>

L'extrait du contrôleur est:

   self.model = {
      closeOthers : false,
      opened   : new Array(),
      topics   : undefined
   };

Les 'sujets' sont renseignés lors D'un appel AJAX. La séparation de l'état "ouvert" des objets de modèle mis à jour à partir du serveur signifie que l'état est préservé entre les actualisations.

Je déclare également que le contrôleur avec ng-controller="CaseController as controller"

1
répondu Gary Murphy 2014-09-30 15:03:03

Accordéon-contrôleur.js

MyApp.Controllers
    .controller('AccordionCtrl', ['$scope', function ($scope) {

        $scope.groups = [
            {
                title: "Dynamic Group Header - 1",
                content: "Dynamic Group Body - 1",
                open: false
            },
            {
                title: "Dynamic Group Header - 2",
                content: "Dynamic Group Body - 2",
                open: false

            },
            {
                title: "Dynamic Group Header - 3",
                content: "Dynamic Group Body - 3",
                open: false
            }
        ];

        /**
         * Open panel method
         * @param idx {Number} - Array index
         */
        $scope.openPanel = function (idx) {
            if (!$scope.groups[idx].open) {
                console.log("Opened group with idx: " + idx);
                $scope.groups[idx].open = true;
            }
        };

        /**
         * Close panel method
         * @param idx {Number} - Array index
         */
        $scope.closePanel = function (idx) {
            if ($scope.groups[idx].open) {
                console.log("Closed group with idx: " + idx);
                $scope.groups[idx].open = false;
            }
        };

    }]);

Index.html

<div ng-controller="AccordionCtrl">

    <accordion>

        <accordion-group ng-repeat="group in groups" is-open="group.open">
            <button ng-click="closePanel($index)">Close me</button>
            {{group.content}}
        </accordion-group>


        <button ng-click="openPanel(0)">Set 1</button>
        <button ng-click="openPanel(1)">Set 2</button>
        <button ng-click="openPanel(2)">Set 3</button>

    </accordion>
</div>
0
répondu Elise Chant 2014-04-01 07:28:35

Voici une solution inspirée de la réponse de kjv, qui suit facilement quel élément d'accordéon est ouvert. J'ai trouvé difficile d'obtenir ng-click pour travailler sur l'en-tête de l'accordéon, bien qu'entourant l'élément dans une balise <span> et ajoutant le ng-click à cela a bien fonctionné.

Un autre problème que j'ai rencontré était, bien que les éléments accordion aient été ajoutés à la page par programme, le contenu ne l'était pas. Quand j'ai essayé de charger le contenu en utilisant des directives angulaires (ie. {{path}}) lié à une variable $scope I serait frappé avec undefined, d'où l'utilisation de la méthode ci-dessous qui remplit le contenu de l'accordéon en utilisant L'ID div intégré dans.

Contrôleur:

    //initialise the open state to false
    $scope.routeDescriptors[index].openState == false

    function opened(index) 
    {
        //we need to track what state the accordion is in
        if ($scope.routeDescriptors[index].openState == true){   //close an accordion
            $scope.routeDescriptors[index].openState == false
        } else {    //open an accordion
            //if the user clicks on another accordion element
            //then the open element will be closed, so this will handle it
            if (typeof $scope.previousAccordionIndex !== 'undefined') {
                $scope.routeDescriptors[$scope.previousAccordionIndex].openState = false;
            }
            $scope.previousAccordionIndex = index;
            $scope.routeDescriptors[index].openState = true;
    }

    function populateDiv(id)
    {
        for (var x = 0; x < $scope.routeDescriptors.length; x++)
        {
            $("#_x" + x).html($scope.routeDescriptors[x]);
        }
    }

HTML:

        <div ng-hide="hideDescriptions" class="ng-hide" id="accordionrouteinfo" ng-click="populateDiv()">
            <accordion>
                <accordion-group ng-repeat="path in routeDescriptors track by $index">
                    <accordion-heading>
                        <span ng-click="opened($index)">route {{$index}}</span>
                    </accordion-heading>
                    <!-- Notice these divs are given an ID which corresponds to it's index-->
                    <div id="_x{{$index}}"></div>
                </accordion-group>
            </accordion>
        </div>
0
répondu JohnDoe 2014-04-10 22:04:20

Vous pouvez le faire avec une directive angulaire:

Html

<div uib-accordion-group is-open="property.display_detail" ng-repeat="property in properties">
  <div uib-accordion-heading ng-click="property.display_detail = ! property.display_detail">
    some heading text
  </div>
  <!-- here is the accordion body -->
  <div ng-init="i=$index">  <!-- I keep track of the index of ng-repeat -->
    <!-- and I call a custom directive -->
    <mydirective mydirective_model="properties" mydirective_index="{% verbatim ng %}{{ i }}{% endverbatim ng %}">
      here is the body
    </mydirective>
  </div>
</div>

Js

app.directive("mydirective", function() {
  return {
    restrict: "EAC",  
    link: function(scope, element, attrs) {
      /* note that ng converts everything to camelCase */
      var model = attrs["mydirectiveModel"];
      var index = attrs["mydirectiveIndex"];
      var watched_name = model + "[" + index + "].display_detail"
      scope.$watch(watched_name, function(is_displayed) {
        if (is_displayed) {
          alert("you opened something");
        }
        else {
          alert("you closed something");
        }
      });
    }
  }
});

Il y a quelques idiosyncrasies à propos de ma configuration (j'utilise Django, d'où les balises " {%verbatim %}"), mais la méthode devrait fonctionner.

0
répondu trubliphone 2015-10-14 19:47:59