【问题标题】:Handle open/collapse events of Accordion in Angular在 Angular 中处理 Accordion 的打开/折叠事件
【发布时间】:2013-03-16 13:03:46
【问题描述】:

如果我有这个代码:

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

使用 AngularJS、angular-ui 和 Twitter Bootstrap,是否可以让手风琴在打开时调用一些动作?我知道我不能简单地添加ng-click,因为它在“编译”到 HTML 以打开/折叠组之后已经使用。

【问题讨论】:

    标签: javascript twitter-bootstrap angularjs angular-ui


    【解决方案1】:

    Accordion 组还允许使用手风琴标题指令,而不是将其作为属性提供。您可以使用它,然后通过 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

    【讨论】:

    • 是的,手风琴式标题允许我们在点击时打开或关闭面板,但这并不是展示如何从其他触发器打开面板的示例。
    • 这是我能找到的最干净的解决方案。谢谢,给我喝杯啤酒!
    • 这很好,唯一的问题是手风琴标题占据了整行,打开的函数只有在你直接点击文本时才会被调用
    • 很酷的方式! +1请注意不要在第一次尝试中放弃,通过使用警报或一些console.log测试点击。它不起作用,请尝试使用控制器范围内存在的功能 :) 我理解它不起作用的原因是因为 ng-click 不支持警报和控制台。
    • 谢谢@AnmolSaraf:我想用警报测试它变得很疯狂!
    【解决方案2】:

    这是一个基于 pkozlowski.opensource 解决方案的解决方案。
    您可以使用动态定义的属性,而不是在集合的每个项目上添加 $watch。在这里,您可以将 groupIsOpened 属性绑定到 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...
          }
        }
      });
    });
    

    使用 properties 而不是 watches 更适合性能。

    【讨论】:

    • 我知道,所以基本上不鼓励 OMGthankyou-thankyou-thankyou 之类的东西。所以让我改为说,如果我遇到你,我会给你买杯啤酒;)这是一个很好的解决方案,没有手表的开销。
    • 这其实是目前最好的答案。
    【解决方案3】:

    手风琴组上有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 中打开一个问题

    【讨论】:

    • 我可以让观看更通用吗?喜欢 - 所以我不必为每一行写'groups[0].open'。我事先不知道我会有多少行......
    • @Michal 恐怕目前的实现并非微不足道。你可以像下面这样设置一个深度手表:plnkr.co/edit/bLnkvf?p=preview,但我真的不推荐这个......
    • @pkozlowski.opensource 我们是否有带有accordion-heading 标记的is-open 选项?
    • @pkozlowski.opensource - 谢谢你的例子。这当然是可行的,但是你们打算正式向 Accordion 控件添加打开/关闭事件吗?原始 Bootstrap 可折叠控件有。
    • 我不敢相信这不是更容易做到的。完全是垃圾。当然,开发人员会希望在开放组中观看手风琴以及何时更改。这是微不足道的。
    【解决方案4】:

    这是一个受 kjv 回答启发的解决方案,它可以轻松跟踪哪个手风琴元素是打开的。我发现很难让ng-click 处理手风琴标题,尽管将元素包围在&lt;span&gt; 标记中并添加ng-click 效果很好。

    我遇到的另一个问题是,虽然accordion 元素是通过编程方式添加到页面中的,但内容却没有。当我尝试使用链接到$scope 变量的Angular 指令(即{{path}})加载内容时,我会被undefined 击中,因此使用了使用ID 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>
    

    【讨论】:

      【解决方案5】:

      我使用关联数组来创建打开状态和模型对象之间的关系。

      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>
      

      控制器sn-p是:

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

      “主题”在 AJAX 调用中填充。将“打开”状态与从服务器更新的模型对象分开意味着状态会在刷新后保留。

      我还用ng-controller="CaseController as controller"声明控制器

      【讨论】:

        【解决方案6】:

        手风琴控制器.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>
        

        【讨论】:

          【解决方案7】:

          您可以使用 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 %}”标签),但该方法应该可以工作。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-01-20
            • 2011-12-18
            • 2014-03-12
            • 1970-01-01
            • 1970-01-01
            • 2013-09-20
            • 2013-07-15
            • 2012-01-14
            相关资源
            最近更新 更多