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.
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
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>
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.
, 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"
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>
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>
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.