Обработка событий открытия/свертывания Accordion в Angular

Если у меня есть этот код:

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

Используя AngularJS, angular-ui и Twitter Bootstrap, можно ли заставить аккордеон вызывать какое-то действие при открытии? Я знаю, что не могу просто добавить ng-click, потому что это уже используется после того, как оно «скомпилировано» в HTML для открытия/свертывания группы.


person Michal    schedule 26.03.2013    source источник


Ответы (7)


В группе аккордеонов есть атрибут is-open, который указывает на привязываемое выражение. Вы можете посмотреть это выражение и выполнить некоторую логику, когда данная группа аккордеона открыта. Используя эту технику, вы измените свою разметку на:

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

чтобы вы могли в контроллере подготовить желаемое выражение просмотра:

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

Хотя описанное выше работает, на практике это может быть немного громоздко, поэтому, если вы считаете, что это можно улучшить, откройте вопрос в https://github.com/angular-ui/bootstrap

person pkozlowski.opensource    schedule 26.03.2013
comment
Могу ли я сделать просмотр более общим? Например, так что мне не нужно писать 'groups[0].open' для каждой строки. Я не знаю заранее, сколько строк у меня будет... - person Michal; 26.03.2013
comment
@Michal Боюсь, что с текущей реализацией это нетривиально. Вы можете настроить глубокую проверку, как в этом планке: plnkr.co/edit/bLnkvf?p=preview но я не могу рекомендовать это на самом деле... - person pkozlowski.opensource; 26.03.2013
comment
@pkozlowski.opensource У нас есть опция is-open с разметкой accordion-heading? - person callmekatootie; 23.10.2013
comment
@pkozlowski.opensource - спасибо за пример. Это, безусловно, выполнимо, но планируете ли вы, ребята, официально добавить события открытия/закрытия в управление Accordion? У оригинального элемента управления Bootstrap Collapsible они есть. - person vkelman; 11.03.2014
comment
Я не могу поверить, что это не проще сделать. Это полный мусор. Конечно, разработчик хотел бы наблюдать за аккордеоном для открытой группы и когда он меняется. Это тривиально. - person Cory Danielson; 24.04.2014
comment
Это действительно все еще текущее состояние этого вопроса? Это неприемлемо - person Brad Orego; 09.07.2014

Группы аккордеона также позволяют использовать директиву заголовка аккордеона вместо предоставления ее в качестве атрибута. Вы можете использовать это, а затем обернуть заголовок другим тегом с помощью 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>

Пример: http://plnkr.co/edit/B3LC1X?p=preview.

person kjv    schedule 28.08.2013
comment
Да, заголовок-аккордеон позволяет нам открывать или закрывать панели по клику, но это не пример, показывающий, как открывать панели из других триггеров. - person Elise Chant; 01.04.2014
comment
Это было самое чистое решение, которое я смог найти. Спасибо, выпей за меня пива! - person krex; 11.04.2014
comment
Это хорошо, единственная проблема заключается в том, что заголовок аккордеона занимает всю строку, а открытая функция будет вызываться только при нажатии непосредственно на текст. - person JMK; 29.05.2014
comment
классный способ! +1 Просто остерегайтесь сдаться с первой попытки, проверив щелчок с предупреждением или каким-нибудь console.log. Это не сработает, попробуйте это с функцией, присутствующей в вашей области на контроллере :) Причина, по которой я понимаю, что это не работает, заключается в том, что оповещение и консоль не поддерживаются в ng-click. - person Anmol Saraf; 15.01.2015
comment
СПАСИБО @AnmolSaraf: я сходил с ума, пытаясь проверить это с помощью предупреждения! - person hugsbrugs; 26.01.2015

Вот решение, основанное на решении pkozlowski.opensource.
Вместо добавления $watch к каждому элементу коллекции вы можете использовать динамически определяемое свойство. . Здесь вы можете связать свойство IsOpened группы group с атрибутом is-open.

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

Таким образом, вы можете динамически добавлять свойство IsOpened к каждому элементу коллекции в контроллере:

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

Использование свойств вместо отслеживания лучше подходит для производительности.

person Khonsort    schedule 18.03.2015
comment
Я знаю, что SO в основном обескураживает Боже мой, спасибо-спасибо-спасибо и тому подобное. Так что позвольте мне вместо этого сказать, что я куплю вам пиво, если когда-нибудь столкнусь с вами ;) Это прекрасное решение без накладных расходов на часы. - person Frederik Struck-Schøning; 15.12.2015
comment
На самом деле это лучший ответ на данный момент. - person asktomsk; 12.01.2016

Я использовал ассоциативный массив для создания связи между открытым состоянием и объектом модели.

HTML-код:

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

Фрагмент контроллера:

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

«Темы» заполняются при вызове AJAX. Отделение «открытого» состояния от объектов модели, которые обновляются с сервера, означает, что состояние сохраняется при обновлениях.

Я также объявляю контроллер с помощью ng-controller="CaseController as controller"

person Gary Murphy    schedule 30.09.2014

аккордеон-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>
person Elise Chant    schedule 01.04.2014

Вот решение, вдохновленное ответом kjv, которое легко отслеживает, какой элемент аккордеона открыт. Мне было трудно заставить ng-click работать с заголовком аккордеона, хотя окружить элемент тегом <span> и добавить ng-щелчок к этому сработало нормально.

Еще одна проблема, с которой я столкнулся, заключалась в том, что, хотя элементы accordion были добавлены на страницу программно, содержимое — нет. Когда я попытался загрузить содержимое с помощью директив Angular (т.е. {{path}}), связанных с переменной $scope, я получил бы undefined, поэтому я использовал метод ниже, который заполняет содержимое аккордеона, используя встроенный в него идентификатор div.

Контроллер:

    //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>
person krex    schedule 10.04.2014

Вы можете сделать это с директивой Angular:

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

В моей настройке есть некоторые особенности (я использую Django, поэтому теги "{% verbatim %}"), но этот метод должен работать.

person trubliphone    schedule 14.10.2015