'use strict';
angular.module('mgcrea.ngStrap.collapse', [])
.provider('$collapse', function () {
var defaults = this.defaults = {
animation: 'am-collapse',
disallowToggle: false,
activeClass: 'in',
startCollapsed: false,
allowMultiple: false
};
var controller = this.controller = function ($scope, $element, $attrs) {
var self = this;
// Attributes options
self.$options = angular.copy(defaults);
angular.forEach(['animation', 'disallowToggle', 'activeClass', 'startCollapsed', 'allowMultiple'], function (key) {
if (angular.isDefined($attrs[key])) self.$options[key] = $attrs[key];
});
// use string regex match boolean attr falsy values, leave truthy values be
var falseValueRegExp = /^(false|0|)$/i;
angular.forEach(['disallowToggle', 'startCollapsed', 'allowMultiple'], function (key) {
if (angular.isDefined($attrs[key]) && falseValueRegExp.test($attrs[key])) {
self.$options[key] = false;
}
});
self.$toggles = [];//触发panel存储
self.$targets = [];//panel body存储
self.$viewChangeListeners = [];//其中的函数会以流水线的形式在视图中的值发生变化时被逐一调用。用来渲染view
self.$registerToggle = function (element) {//把element注册到$toggles 中
self.$toggles.push(element);
};
self.$registerTarget = function (element) {//把element注册到panel body存储
self.$targets.push(element);
};
self.$unregisterToggle = function (element) {//在$toggles删除对应的element
var index = self.$toggles.indexOf(element);
// remove toggle from $toggles array
self.$toggles.splice(index, 1);
};
self.$unregisterTarget = function (element) {//在$targets 删除对应的element
var index = self.$targets.indexOf(element);
// remove element from $targets array
self.$targets.splice(index, 1);
if (self.$options.allowMultiple) {//如果允许同时打开多个panel
// remove target index from $active array values
deactivateItem(element);//就把这个active的元素删除
}
// fix active item indexes
fixActiveItemIndexes(index);
self.$viewChangeListeners.forEach(function (fn) {//重新render
fn();
});
};
// use array to store all the currently open panels 存储所有打开的项
self.$targets.$active = !self.$options.startCollapsed ? [0] : [];
//设置哪个element显示
self.$setActive = $scope.$setActive = function (value) {
if (angular.isArray(value)) {
self.$targets.$active = value;
} else if (!self.$options.disallowToggle && isActive(value)) {
deactivateItem(value);
} else {
activateItem(value);
}
self.$viewChangeListeners.forEach(function (fn) {
fn();
});
};
//起始状态
self.$activeIndexes = function () {
if (self.$options.allowMultiple) {
return self.$targets.$active;
}
return self.$targets.$active.length === 1 ? self.$targets.$active[0] : -1;
};
function fixActiveItemIndexes (index) {
// item with index was removed, so we
// need to adjust other items index values
var activeIndexes = self.$targets.$active;
for (var i = 0; i < activeIndexes.length; i++) {
if (index < activeIndexes[i]) {
activeIndexes[i] = activeIndexes[i] - 1;
}
// the last item is active, so we need to
// adjust its index
if (activeIndexes[i] === self.$targets.length) {
activeIndexes[i] = self.$targets.length - 1;
}
}
}
//判断value是否是active
function isActive (value) {
var activeItems = self.$targets.$active;
return activeItems.indexOf(value) !== -1;
}
//删除active的元素
function deactivateItem (value) {
var index = self.$targets.$active.indexOf(value);
if (index !== -1) {
self.$targets.$active.splice(index, 1);
}
}
//添加当前点击的element到$active
function activateItem (value) {
if (!self.$options.allowMultiple) {//不允许打开多个panel
// remove current selected item
self.$targets.$active.splice(0, 1);//关闭当前的
}
if (self.$targets.$active.indexOf(value) === -1) {//如果当前的value不在active里就加入
self.$targets.$active.push(value);
}
}
};
this.$get = function () {
var $collapse = {};
$collapse.defaults = defaults;
$collapse.controller = controller;
return $collapse;
};
})
.directive('bsCollapse', function ($window, $animate, $collapse) {
return {
require: ['?ngModel', 'bsCollapse'],
controller: ['$scope', '$element', '$attrs', $collapse.controller],
link: function postLink (scope, element, attrs, controllers) {
var ngModelCtrl = controllers[0];
var bsCollapseCtrl = controllers[1];
if (ngModelCtrl) {
// Update the modelValue following更新modelValue
bsCollapseCtrl.$viewChangeListeners.push(function () {
ngModelCtrl.$setViewValue(bsCollapseCtrl.$activeIndexes());
});
// modelValue -> $formatters -> viewValue
ngModelCtrl.$formatters.push(function (modelValue) {
// console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
if (angular.isArray(modelValue)) {
// model value is an array, so just replace
// the active items directly
bsCollapseCtrl.$setActive(modelValue);
} else {
var activeIndexes = bsCollapseCtrl.$activeIndexes();
if (angular.isArray(activeIndexes)) {
// we have an array of selected indexes
if (activeIndexes.indexOf(modelValue * 1) === -1) {
// item with modelValue index is not active
bsCollapseCtrl.$setActive(modelValue * 1);
}
} else if (activeIndexes !== modelValue * 1) {
bsCollapseCtrl.$setActive(modelValue * 1);
}
}
return modelValue;
});
}
}
};
})
.directive('bsCollapseToggle', function () {
return {
require: ['^?ngModel', '^bsCollapse'],
link: function postLink (scope, element, attrs, controllers) {
// var ngModelCtrl = controllers[0];
var bsCollapseCtrl = controllers[1];
// Add base attr
element.attr('data-toggle', 'collapse');
// Push pane to parent bsCollapse controller
bsCollapseCtrl.$registerToggle(element);
// remove toggle from collapse controller when toggle is destroyed
scope.$on('$destroy', function () {
bsCollapseCtrl.$unregisterToggle(element);
});
element.on('click', function () {
if (!attrs.disabled) {//判断bs-collapse-toggle是否是指令
var index = attrs.bsCollapseToggle && attrs.bsCollapseToggle !== 'bs-collapse-toggle' ? attrs.bsCollapseToggle : bsCollapseCtrl.$toggles.indexOf(element);
bsCollapseCtrl.$setActive(index * 1);
scope.$apply();
}
});
}
};
})
.directive('bsCollapseTarget', function ($animate) {
return {
require: ['^?ngModel', '^bsCollapse'],
// scope: true,
link: function postLink (scope, element, attrs, controllers) {
// var ngModelCtrl = controllers[0];
var bsCollapseCtrl = controllers[1];
// Add base class
element.addClass('collapse');
// Add animation class
if (bsCollapseCtrl.$options.animation) {
element.addClass(bsCollapseCtrl.$options.animation);
}
// Push pane to parent bsCollapse controller 注册panel容器
bsCollapseCtrl.$registerTarget(element);
// remove pane target from collapse controller when target is destroyed
scope.$on('$destroy', function () {
bsCollapseCtrl.$unregisterTarget(element);
});
//通过判断当前element在$targets的index,与$activeIndexes中的比较来确定
function render () {
var index = bsCollapseCtrl.$targets.indexOf(element);
var active = bsCollapseCtrl.$activeIndexes();
var action = 'removeClass';
if (angular.isArray(active)) {
if (active.indexOf(index) !== -1) {
action = 'addClass';
}
} else if (index === active) {
action = 'addClass';
}
$animate[action](element, bsCollapseCtrl.$options.activeClass);
}
bsCollapseCtrl.$viewChangeListeners.push(function () {
render();
});
render();
}
};
});