web-dev-qa-db-fra.com

Traiter les événements d'ouverture/fermeture d'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 jouer à l'accordéon une action lorsqu'il est ouvert? Je sais que je ne peux pas simplement ajouter ng-click, car il est déjà utilisé après sa "compilation" en HTML pour l'ouverture/la réduction du groupe.

40
Michal

Il y a l'attribut is-open sur le groupe accordéon qui pointe vers une expression pouvant être liée. Vous pouvez regarder cette expression et exécuter une logique lorsqu'un groupe d'accordéon donné est ouvert. En utilisant cette technique, vous modifieriez 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 fastidieux à utiliser dans la pratique. Si vous pensez que cela pourrait être amélioré, ouvrez un problème dans https://github.com/angular-ui/bootstrap

24

Les groupes accordéon permettent également une directive en-tête accordéon au lieu de la fournir en tant qu'attribut. Vous pouvez utiliser cela et ensuite envelopper votre en-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
kjv

Voici une solution basée sur la solution pkozlowski.opensource.
Au lieu d’ajouter un $ watch à chaque élément de la collection, vous pouvez utiliser un défini dynamiquement Property . Ici, vous pouvez lier la propriété IsOpened de group à l'attribut is-open.

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

Ainsi, vous pouvez ajouter dynamiquement la propriété IsOpened 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...
      }
    }
  });
});

Utiliser propriétés au lieu de montres est meilleur pour les performances. 

29
Khonsort

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

Le 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 de contrôleur est:

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

Les "sujets" sont renseignés lors d'un appel AJAX. Si vous séparez l'état "ouvert" des objets de modèle mis à jour à partir du serveur, l'état est préservé pendant les actualisations.

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

1
Gary Murphy

accordéon-controller.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
Elise Chant

Voici une solution inspirée de la réponse de kjv, qui permet de savoir facilement quel élément d'accordéon est ouvert. J'ai trouvé difficile d'obtenir ng-click pour travailler sur l'en-tête d'accordéon, même si entourer l'élément dans une balise <span> et ajouter 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. Lorsque j'ai essayé de charger le contenu à l'aide de directives angulaires (c.-à-d. {{path}}) liées à une variable $scope, je suis frappé par undefined, d'où l'utilisation de la méthode ci-dessous qui remplit le contenu accordéon en utilisant l'ID div intégré.

Manette:

    //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
JohnDoe

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 particularités sur ma configuration ici (j'utilise Django, d'où les balises "{% verbatim%}"), mais la méthode devrait fonctionner.

0
trubliphone