/*
* 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.
//<uib-accordion close-others="oneAtATime"> </uib-accordion> 对应指令 模板<div class=\"panel-group\" ng-transclude></div>
.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展开或者折叠在一个手风琴效果中
// <uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">This content is straight in the template. </uib-accordion-group> 的指令
.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
//使用手风琴标题下面的手风琴组,提供一个内容标签
//对应指令 <uib-accordion-heading> I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i> </uib-accordion-heading>
.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
//<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
.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';//替换成 <div class=\"panel-group\" ng-transclude></div>
},
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', [])
//<div class="panel-collapse collapse" uib-collapse="!isOpen">的指令
.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",
"<div class=\"panel {{panelClass || 'panel-default'}}\">\n" +
" <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
" <h4 class=\"panel-title\">\n" +
" <a href tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
" </h4>\n" +
" </div>\n" +
" <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
" <div class=\"panel-body\" ng-transclude></div>\n" +
" </div>\n" +
"</div>\n" +
"");
}]);
angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/accordion/accordion.html",
"<div class=\"panel-group\" ng-transclude></div>");
}]);
<div ng-controller="AccordionDemoCtrl">
<script type="text/ng-template" id="group-template.html">
<div class="panel {{panelClass || 'panel-default'}}">
<div class="panel-heading">
<h4 class="panel-title" style="color:#fa39c3">
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span
ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
</h4>
</div>
<div class="panel-collapse collapse" uib-collapse="!isOpen">
<div class="panel-body" style="text-align: right" ng-transclude></div>
</div>
</div>
</script>
<p>
<button type="button" class="btn btn-default btn-sm" ng-click="status.open = !status.open">Toggle last panel</button>
<button type="button" class="btn btn-default btn-sm" ng-click="status.isFirstDisabled = ! status.isFirstDisabled">Enable / Disable first panel</button>
</p>
<div class="checkbox">
<label>
<input type="checkbox" ng-model="oneAtATime">
Open only one at a time
</label>
</div>
<uib-accordion close-others="oneAtATime">
<uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
This content is straight in the template.
</uib-accordion-group>
<uib-accordion-group heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</uib-accordion-group>
<uib-accordion-group heading="Dynamic Body Content">
<p>The body of the uib-accordion group grows to fit the contents</p>
<button type="button" class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
<div ng-repeat="item in items">{{item}}</div>
</uib-accordion-group>
<uib-accordion-group heading="Custom template" template-url="group-template.html">
Hello
</uib-accordion-group>
<uib-accordion-group heading="Delete account" panel-class="panel-danger">
<p>Please, to delete your account, click the button below</p>
<button class="btn btn-danger">Delete</button>
</uib-accordion-group>
<uib-accordion-group is-open="status.open">
<uib-accordion-heading>
I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</uib-accordion-heading>
This is just some content to illustrate fancy headings.
</uib-accordion-group>
</uib-accordion>
</div>
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进行监听,就可以得到不同的效果。