Lidar com events de abertura / colapso do Acordeão em Angular

Se eu tiver este código:

 {{group.content}}  

Usando AngularJS, angular-ui e Twitter Bootstrap, é possível fazer o acordeão chamar alguma ação quando aberto? Eu sei que não posso simplesmente adicionar ng-click , porque isso já é usado depois de ser “compilado” para HTML para abrir / recolher o grupo.

Existe o atributo is-open no grupo acordeão que aponta para uma expressão ligável. Você pode observar essa expressão e executar alguma lógica quando um determinado grupo de acordeão estiver aberto. Usando essa técnica, você alteraria sua marcação para:

  {{group.content}}  

para que você possa, no controlador, preparar uma expressão de observação desejada:

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

Enquanto o acima funciona, pode ser um pouco pesado para usar na prática, então se você acha que isso poderia ser melhorado, abra um problema em https://github.com/angular-ui/bootstrap

Os grupos de acordeões também permitem uma diretiva de header de acordeão em vez de fornecê-lo como um atributo. Você pode usar isso e, em seguida, encapsular seu header em outra tag com um ng-click.

   {{group.content}}   

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

Aqui está uma solução baseada na solução pkozlowski.opensource .
Em vez de adicionar um $ watch em cada item da coleção, você pode usar uma propriedade definida dinamicamente . Aqui, você pode vincular a propriedade IsOpened do grupo ao atributo is-open .

  {{group.content}}  

Assim, você pode adicionar dinamicamente a propriedade IsOpened em cada item da coleção no controlador:

 $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... } } }); }); 

Usar propriedades em vez de relógios é melhor para performances.

Eu usei um array associativo para criar um relacionamento entre o estado aberto e o object de modelo.

O HTML é:

  

{{topic.title}} Updated: {{topic.updatedDate}}

Topic Description

{{topic.description}}

  • Comment by: {{comment.author}}Updated: {{comment.updatedDate}} | {{comment.updatedTime}}

    {{comment.comment}}

O snippet do controlador é:

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

Os ‘tópicos’ são preenchidos em uma chamada AJAX. Separar o estado ‘aberto’ dos objects do modelo atualizados do servidor significa que o estado é preservado nas atualizações.

Eu também declaro o controlador com ng-controller="CaseController as controller"

accordion-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

 
{{group.content}}

Aqui está uma solução inspirada na resposta do kjv, que rastreia facilmente qual elemento do acordeon está aberto. Eu achei difícil fazer com que o ng-click trabalhasse no header do acordeão, apesar de cercar o elemento em uma tag e adicionar o ng-click para que funcionasse bem.

Outro problema que encontrei foi, embora os elementos accordion foram adicionados à página por meio de programação, o conteúdo não foi. Quando eu tentei carregar o conteúdo usando diretivas angulares (ou seja, {{path}} ) vinculado a uma variável $scope eu seria atingido com undefined , daí o uso do método abaixo que preenche o conteúdo acordeão usando o ID div embutido.

Controlador:

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

  
route {{$index}}

Você pode fazê-lo com uma diretiva angular:

html

 
some heading text
here is the body

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"); } }); } } }); 

Existem algumas idiossincrasias sobre minha configuração lá (eu uso o Django, daí as tags “{% verbatim%}”), mas o método deve funcionar.