Angular-UI手风琴效果源码分析

/*
 * angular-ui-bootstrap
 * http://angular-ui.github.io/bootstrap/

 * Version: 0.14.3 - 2015-10-23
 * License: MIT
 */
angular.module("ui.bootstrap", ["ui.bootstrap.tpls","ui.bootstrap.accordion","ui.bootstrap.collapse"]);
angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html"]);
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])

.constant('uibAccordionConfig', {//constant 可以将一个已经存在的变量值注册为服务
  closeOthers: true
})

.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
  // This array keeps track of the accordion groups
  this.groups = [];//用数组来保存所有的手风琴组

  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to //确保所有的手风琴组是关闭的,除非其它关闭的并不是
  this.closeOthers = function(openGroup) { //关闭函数 如果oneAtATime存在且为true
    var closeOthers = angular.isDefined($attrs.closeOthers) ?  $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;//判断$attrs.closeOthers是否定义过
    if (closeOthers) {
      angular.forEach(this.groups, function(group) { //遍历所有的手风琴组
        if (group !== openGroup) { //如果没有规定打开的手风琴组,就全部关闭
          group.isOpen = false;
        }
      });
    }
  };

  // This is called from the accordion-group directive to add itself to the accordion //要向手风琴组添加对象时调用的函数
  this.addGroup = function(groupScope) { //添加的是作用域
    var that = this;
    this.groups.push(groupScope);

    groupScope.$on('$destroy', function(event) {//当监听到销毁,就移除手风琴组
      that.removeGroup(groupScope);
    });
  };

  // This is called from the accordion-group directive when to remove itself //要移除手风琴组时候调用,需要移除数组中对应的项
  this.removeGroup = function(group) { 
    var index = this.groups.indexOf(group);
    if (index !== -1) {
      this.groups.splice(index, 1);
    }
  };

}])

    // The accordion directive simply sets up the directive controller 这个指令只是建立了这个指令的控制器和添加了一个css类型
    // and adds an accordion CSS class to itself element.
    //   对应指令 模板
.directive('uibAccordion', function() { return { controller: 'UibAccordionController', controllerAs: 'accordion',//控制器别名 transclude: true,//可以替换 templateUrl: function(element, attrs) { // return attrs.templateUrl || 'template/accordion/accordion.html'; } }; }) // The accordion-group directive indicates a block of html that will expand and collapse in an 这个指令表示出一段html展开或者折叠在一个手风琴效果中 // This content is straight in the template. 的指令 .directive('uibAccordionGroup', function() { return { require: '^uibAccordion', // We need this directive to be inside an accordion //我们需要这个指令来在其中放入一个手风琴效果 transclude: true, // It transcludes the contents of the directive into the template 允许这个指令的内容到模板中进行替换 replace: true, // The element containing the directive will be replaced with the template templateUrl: function(element, attrs) { //调用模板 return attrs.templateUrl || 'template/accordion/accordion-group.html'; }, scope: { heading: '@', // Interpolate the heading attribute onto this scope //传递的是字符串 isOpen: '=?', //=双向绑定 isDisabled: '=?' //?告诉指令如果没有找到依赖的指令,不要抛出异常。 }, controller: function() { //设置当前对象的heading this.setHeading = function(element) { this.heading = element; }; }, link: function(scope, element, attrs, accordionCtrl) { accordionCtrl.addGroup(scope);//把当前scope添加进 手风琴数组 scope.openClass = attrs.openClass || 'panel-open';//展开的样式 scope.panelClass = attrs.panelClass;//获取具体定义的类名 scope.$watch('isOpen', function(value) {//监听isOpen,判断是否发生变化 element.toggleClass(scope.openClass, !!value);//如果isOpen为ture时,就添加展开的样式 if (value) { accordionCtrl.closeOthers(scope); } }); scope.toggleOpen = function($event) { //手风琴开关函数 if (!scope.isDisabled) { //scope.isDisabled为false时执行 if (!$event || $event.which === 32) { scope.isOpen = !scope.isOpen; } } }; } }; }) // Use accordion-heading below an accordion-group to provide a heading containing HTML //使用手风琴标题下面的手风琴组,提供一个内容标签 //对应指令 I can have markup, too! .directive('uibAccordionHeading', function() { return { transclude: true, // Grab the contents to be used as the heading template: '', // In effect remove this element! replace: true, require: '^uibAccordionGroup', link: function(scope, element, attrs, accordionGroupCtrl, transclude) { // Pass the heading to the accordion-group controller // so that it can be transcluded into the right place in the template // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); } }; }) // Use in the accordion-group template to indicate where you want the heading to be transcluded // You must provide the property on the accordion-group controller that will hold the transcluded element //{{heading}} .directive('uibAccordionTransclude', function() { return { require: ['?^uibAccordionGroup', '?^accordionGroup'], link: function(scope, element, attrs, controller) { controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {//{{heading}} if (heading) { element.find('span').html(''); element.find('span').append(heading); } }); } }; }); /* Deprecated accordion below //弃用的手风琴 dead code angular.module('ui.bootstrap.accordion') .value('$accordionSuppressWarning', false) // .controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) { if (!$accordionSuppressWarning) { $log.warn('AccordionController is now deprecated. Use UibAccordionController instead.'); } angular.extend(this, $controller('UibAccordionController', { //扩展当前控制器 $scope: $scope, $attrs: $attrs })); //加载控制器并传入一个作用域,同AngularJS在运行时做的一样 }]) .directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { return { restrict: 'EA', controller: 'AccordionController', controllerAs: 'accordion', //控制器别名 transclude: true, replace: false, templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/accordion/accordion.html';//替换成
}, link: function() { if (!$accordionSuppressWarning) { $log.warn('accordion is now deprecated. Use uib-accordion instead.'); } } }; }]) .directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { return { require: '^accordion', // We need this directive to be inside an accordion restrict: 'EA', transclude: true, // It transcludes the contents of the directive into the template replace: true, // The element containing the directive will be replaced with the template templateUrl: function(element, attrs) { return attrs.templateUrl || 'template/accordion/accordion-group.html'; }, scope: { heading: '@', //传递字符串值 // Interpolate the heading attribute onto this scope isOpen: '=?', //=双向绑定 isDisabled: '=?' //=双向绑定 }, controller: function() { //让这个指令的heading等于传入的参数 this.setHeading = function(element) { this.heading = element; }; }, link: function(scope, element, attrs, accordionCtrl) { if (!$accordionSuppressWarning) { $log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.'); } accordionCtrl.addGroup(scope); scope.openClass = attrs.openClass || 'panel-open'; scope.panelClass = attrs.panelClass; scope.$watch('isOpen', function(value) { element.toggleClass(scope.openClass, !!value); if (value) { accordionCtrl.closeOthers(scope); } }); scope.toggleOpen = function($event) { if (!scope.isDisabled) { if (!$event || $event.which === 32) { scope.isOpen = !scope.isOpen; } } }; } }; }]) .directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { return { restrict: 'EA', transclude: true, // Grab the contents to be used as the heading template: '', // In effect remove this element! replace: true, require: '^accordionGroup', link: function(scope, element, attr, accordionGroupCtrl, transclude) { if (!$accordionSuppressWarning) { $log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.'); } // Pass the heading to the accordion-group controller // so that it can be transcluded into the right place in the template // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); } }; }]) .directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) { return { require: '^accordionGroup', link: function(scope, element, attr, controller) { if (!$accordionSuppressWarning) { $log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.'); } scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { if (heading) { element.find('span').html(''); element.find('span').append(heading); } }); } }; }]); */ //具体内容展示模块,没有独立scope,与父级使用同一个scope angular.module('ui.bootstrap.collapse', []) //
的指令 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) { var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;//判断是否有$animateCss服务注入,如果有就取得这个服务 return { link: function(scope, element, attrs) {//link函数 //展开函数 function expand() { element.removeClass('collapse') .addClass('collapsing') .attr('aria-expanded', true)//aria-expanded表示展开状态。默认为undefined, 表示当前展开状态未知。这里表示展开 .attr('aria-hidden', false);//这里表示关闭 if ($animateCss) {//如果有$animateCss服务,就用来添加动画 $animateCss(element, { addClass: 'in',//in样式就是block easing: 'ease', to: { height: element[0].scrollHeight + 'px' } }).start().finally(expandDone); } else {//如果没有就用ng自带的动画模块 $animate.addClass(element, 'in', { to: { height: element[0].scrollHeight + 'px' } }).then(expandDone);//返回一个promise对象,用then来处理成功回调 } } //展开后回调 function expandDone() {//具体的成功回调函数,用来操作class element.removeClass('collapsing') .addClass('collapse')//display:none .css({height: 'auto'}); } //收缩函数 function collapse() { if (!element.hasClass('collapse') && !element.hasClass('in')) {//如果element没有collapse样式,并且也没有in样式 return collapseDone(); } element // IMPORTANT: The height must be set before adding "collapsing" class. // Otherwise, the browser attempts to animate from height 0 (in // collapsing class) to the given height here. .css({height: element[0].scrollHeight + 'px'}) //设置高度 // initially all panel collapse have the collapse class, this removal // prevents the animation from jumping to collapsed state .removeClass('collapse') //移除collapse,也就是移除display:none .addClass('collapsing') //添加collapsing,高度0 .attr('aria-expanded', false) .attr('aria-hidden', true); if ($animateCss) { $animateCss(element, { removeClass: 'in', to: {height: '0'} }).start().finally(collapseDone); } else { $animate.removeClass(element, 'in', {//设置动画,高度为0 to: {height: '0'} }).then(collapseDone); } } //收缩后回调函数 function collapseDone() { element.css({height: '0'}); // Required so that collapse works when animation is disabled 动画不执行时运行 element.removeClass('collapsing')//collapsing设置高度为0 .addClass('collapse');//display:none } //监听attrs.uibCollapse也就是!isOpen的值,来判断展开和关闭 scope.$watch(attrs.uibCollapse, function(shouldCollapse) { if (shouldCollapse) { collapse(); } else { expand(); } }); } }; }]); /* Deprecated collapse below dead code angular.module('ui.bootstrap.collapse') .value('$collapseSuppressWarning', false) .directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) { var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; return { link: function(scope, element, attrs) { if (!$collapseSuppressWarning) { $log.warn('collapse is now deprecated. Use uib-collapse instead.'); } function expand() { element.removeClass('collapse') .addClass('collapsing') .attr('aria-expanded', true) .attr('aria-hidden', false); if ($animateCss) { $animateCss(element, { easing: 'ease', to: { height: element[0].scrollHeight + 'px' } }).start().done(expandDone); } else { $animate.animate(element, {}, { height: element[0].scrollHeight + 'px' }).then(expandDone); } } function expandDone() { element.removeClass('collapsing') .addClass('collapse in') .css({height: 'auto'}); } function collapse() { if (!element.hasClass('collapse') && !element.hasClass('in')) { return collapseDone(); } element // IMPORTANT: The height must be set before adding "collapsing" class. // Otherwise, the browser attempts to animate from height 0 (in // collapsing class) to the given height here. .css({height: element[0].scrollHeight + 'px'}) // initially all panel collapse have the collapse class, this removal // prevents the animation from jumping to collapsed state .removeClass('collapse in') .addClass('collapsing') .attr('aria-expanded', false) .attr('aria-hidden', true); if ($animateCss) { $animateCss(element, { to: {height: '0'} }).start().done(collapseDone); } else { $animate.animate(element, {}, { height: '0' }).then(collapseDone); } } function collapseDone() { element.css({height: '0'}); // Required so that collapse works when animation is disabled element.removeClass('collapsing') .addClass('collapse'); } scope.$watch(attrs.collapse, function(shouldCollapse) { if (shouldCollapse) { collapse(); } else { expand(); } }); } }; }]); */ angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/accordion/accordion-group.html", "
\n" + "
\n" + "

\n" + " {{heading}}\n" + "

\n" + "
\n" + "
\n" + "
\n" + "
\n" + "
\n" + ""); }]); angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { $templateCache.put("template/accordion/accordion.html", "
"); }]);

This content is straight in the template. {{group.content}}

The body of the uib-accordion group grows to fit the contents

{{item}}
Hello

Please, to delete your account, click the button below

I can have markup, too! This is just some content to illustrate fancy headings.
angular.module('ui.bootstrap.demo').controller('AccordionDemoCtrl', function ($scope) {
  $scope.oneAtATime = true;
  $scope.groups = [
    {
      title: 'Dynamic Group Header - 1',
      content: 'Dynamic Group Body - 1'
    },
    {
      title: 'Dynamic Group Header - 2',
      content: 'Dynamic Group Body - 2'
    }
  ];
  $scope.items = ['Item 1', 'Item 2', 'Item 3'];
  $scope.addItem = function() {
    var newItemNo = $scope.items.length + 1;
    $scope.items.push('Item ' + newItemNo);
  };
  $scope.status = {
    isFirstOpen: true,
    isFirstDisabled: false
  };
});

第一部分是angular插件的源码,后面是官网的示例(https://angular-ui.github.io/bootstrap/)。
插件最核心的就是实现不同指令间的通信,这个插件实现的方式是让两个作用域,UibAccordionController和uibCollapse这两个控制器下的作用域下的实现通信。通信的方式是利用父子指令间,通过scope继承实现的。分别继承(双向绑定)heading\isOpen\isDisabled三个参数。然后在分别的指令中对isOpen进行监听,就可以得到不同的效果。

你可能感兴趣的:(Angular-UI手风琴效果源码分析)