最近看了一下UI-Bootstrap的源码,感觉写的非常好,对自己写angular程序有很大的启发,源码指令也很高,这里先简单分析一下,然后把思路整理一下,如果有错误欢迎指出来。
我用思维导图做了一个模块依赖关系,可以结合看一下,以下是连接https://www.processon.com/view/link/565d9d78e4b00e1bc065a30b
/*
* 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.modal","ui.bootstrap.stackedMap"]);
angular.module("ui.bootstrap.tpls", ["template/modal/backdrop.html","template/modal/window.html"]);
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
/**
* A helper, internal data structure that stores all references attached to key
*/
.factory('$$multiMap', function() {//生成一个map数据结构用来保存
return {
createNew: function() {
var map = {};//内部数据,声明周期是整个app的声明周期,用来保存数据
return {
entries: function() {//生成一个map数据结构
return Object.keys(map).map(function(key) {//Object.keys(keys).map(function(key){}) Object.keys(keys)返回keys上的所有属性和方法
return {
key: key,
value: map[key]
};
});
},
get: function(key) {//get 获取map上对应key的value
return map[key];
},
hasKey: function(key) {//判断map数据结构上是否有key
return !!map[key]; //把!!强制转换为布尔类型
},
keys: function() { //返回map数据结构上的所有属性和方法
return Object.keys(map);
},
put: function(key, value) {//把key-value放入map
if (!map[key]) {
map[key] = []; //一个key可以对应多个value
}
map[key].push(value);
},
remove: function(key, value) {// 移除key-vaue值
var values = map[key];
if (!values) {
return;
}
var idx = values.indexOf(value);
if (idx !== -1) {
values.splice(idx, 1);//splice(index,1)
}
if (!values.length) {
delete map[key];
}
}
};
}
};
})
/**
* A helper directive for the $modal service. It creates a backdrop element.
*/
.directive('uibModalBackdrop', [ //背景指令
'$animate', '$injector', '$uibModalStack',
function($animate , $injector, $modalStack) {
var $animateCss = null;
if ($injector.has('$animateCss')) { //判断$animateCss是否注入到服务中
$animateCss = $injector.get('$animateCss');//如果有获取
}
return {
replace: true,
templateUrl: 'template/modal/backdrop.html',
compile: function(tElement, tAttrs) {
tElement.addClass(tAttrs.backdropClass); //为element添加backdrop-class属性对应的类名
return linkFn;
}
};
function linkFn(scope, element, attrs) {
// Temporary fix for prefixing
element.addClass('modal-backdrop');//为element添加modal-backdrop类
if (attrs.modalInClass) {//如果属性集合中有modal-in-class
if ($animateCss) {//并且有$animateCss
$animateCss(element, {//调用函数
addClass: attrs.modalInClass
}).start();
} else {
$animate.addClass(element, attrs.modalInClass);// 使用angular自定义动画,添加属性集合中modal-in-class对应的类
}
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {//监听$modalStack.NOW_CLOSING_EVENT事件
var done = setIsAsync();
if ($animateCss) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
$animate.removeClass(element, attrs.modalInClass).then(done);// 使用angular自定义动画,添加属性集合中modal-in-class对应的类
}
});
}
}
}])
.directive('uibModalWindow', [//模态窗口指令
'$uibModalStack', '$q', '$animate', '$injector',
function($modalStack , $q , $animate, $injector) {
var $animateCss = null;
if ($injector.has('$animateCss')) {//判断是否注入了$animateCss这个服务
$animateCss = $injector.get('$animateCss');//如果有就获取
}
return {
scope: {
index: '@'//同父scope的index属性进行绑定
},
replace: true,
transclude: true,
templateUrl: function(tElement, tAttrs) { //模板
return tAttrs.templateUrl || 'template/modal/window.html';
},
link: function(scope, element, attrs) {
element.addClass(attrs.windowClass || '');//为element添加属性集合attrs中window-class属性对应的类,就是in
element.addClass(attrs.windowTopClass || '');//为element添加属性集合attrs中window-top-class属性对应的类
scope.size = attrs.size;
scope.close = function(evt) { //关闭方法
var modal = $modalStack.getTop(); //获取$uibModalStack顶部栈
if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
evt.preventDefault(); //阻止默认方法
evt.stopPropagation();//阻止事件冒泡
$modalStack.dismiss(modal.key, 'backdrop click');//调用$uibModalStack中返回的dismiss方法,解除key-value
}
};
// moved from template to fix issue #2280
element.on('click', scope.close);
// This property is only added to the scope for the purpose of detecting when this directive is rendered.
// We can detect that by using this property in the template associated with this directive and then use
// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
scope.$isRendered = true;
// Deferred object that will be resolved when this modal is render.
var modalRenderDeferObj = $q.defer();//获取一个异步对象
// Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
// In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
attrs.$observe('modalRender', function(value) {
if (value == 'true') {
modalRenderDeferObj.resolve();
}
});
modalRenderDeferObj.promise.then(function() {
var animationPromise = null;
if (attrs.modalInClass) {//同上都是来判断用什么动画方法
if ($animateCss) {
animationPromise = $animateCss(element, {
addClass: attrs.modalInClass
}).start();
} else {
animationPromise = $animate.addClass(element, attrs.modalInClass);
}
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
var done = setIsAsync();
if ($animateCss) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
$animate.removeClass(element, attrs.modalInClass).then(done);
}
});
}
$q.when(animationPromise).then(function() {
var inputWithAutofocus = element[0].querySelector('[autofocus]');//获取element中有[autofocus]属性的元素
/**
* Auto-focusing of a freshly-opened modal element causes any child elements
* with the autofocus attribute to lose focus. This is an issue on touch
* based devices which will show and then hide the onscreen keyboard.
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element.
*/
if (inputWithAutofocus) {
inputWithAutofocus.focus(); //获取焦点
} else {
element[0].focus();
}
});
// Notify {@link $modalStack} that modal is rendered.
var modal = $modalStack.getTop();//获取顶部的栈
if (modal) {
$modalStack.modalRendered(modal.key); //渲染顶部栈
}
});
}
};
}])
.directive('uibModalAnimationClass', function() { //uib-modal-animation-class的指令
return {
compile: function(tElement, tAttrs) {
if (tAttrs.modalAnimation) {
tElement.addClass(tAttrs.uibModalAnimationClass);
}
}
};
})
.directive('uibModalTransclude', function() { //ui-modal-transclude指令
return {
link: function($scope, $element, $attrs, controller, $transclude) { //transclude链接函数是实际被执行用来克隆元素和操作DOM的函数
$transclude($scope.$parent, function(clone) {
$element.empty(); //清空element
$element.append(clone);//添加参数元素,这里用来向element添加元素
});
}
};
})
.factory('$uibModalStack', [ //$uibModalStack服务
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
'$$multiMap',
'$$stackedMap',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector,
$$multiMap,
$$stackedMap) {
var $animateCss = null;
if ($injector.has('$animateCss')) { //判断是否有注入的$animateCss动画
$animateCss = $injector.get('$animateCss');//获取
}
var OPENED_MODAL_CLASS = 'modal-open';
var backdropDomEl, backdropScope;
var openedWindows = $$stackedMap.createNew();//$$stackedMap实例
var openedClasses = $$multiMap.createNew();//$$multiMap实例
var $modalStack = {
NOW_CLOSING_EVENT: 'modal.stack.now-closing'
};
//Modal focus behavior
var focusableElementList;
var focusIndex = 0;
var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' + //多去焦点的元素
'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
'iframe, object, embed, *[tabindex], *[contenteditable=true]';
function backdropIndex() { //设置背景z-index值
var topBackdropIndex = -1;
var opened = openedWindows.keys(); //获取openedWindows中map对象所有的属性和方法
for (var i = 0; i < opened.length; i++) {
if (openedWindows.get(opened[i]).value.backdrop) { //遍历openedWindows中value属性中backdrop
topBackdropIndex = i;
}
}
return topBackdropIndex;
}
$rootScope.$watch(backdropIndex, function(newBackdropIndex) { //监听backdropIndex,当有新的值的时候backdropScope.index等于新值
if (backdropScope) {
backdropScope.index = newBackdropIndex;
}
});
function removeModalWindow(modalInstance, elementToReceiveFocus) { //移除模态窗口
var body = $document.find('body').eq(0);//获取body对象
var modalWindow = openedWindows.get(modalInstance).value; //获取openedWindows中modalInstance对应的实例的值
//clean up the stack
openedWindows.remove(modalInstance);//从openedWindows这个数据结构中移除modalInstance这个对象
removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { //移除后动画函数
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; //value值中openedClass的属性值
openedClasses.remove(modalBodyClass, modalInstance);//另一个map结构也移除
body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));//body toggleClass动画
toggleTopWindowClass(true);
});
checkRemoveBackdrop();
//move focus to specified element if available, or else to body
if (elementToReceiveFocus && elementToReceiveFocus.focus) { //获取焦点
elementToReceiveFocus.focus();
} else {
body.focus();
}
}
// Add or remove "windowTopClass" from the top window in the stack 添加或删除windowTopClass的类名在栈顶上
function toggleTopWindowClass(toggleSwitch) {
var modalWindow;
if (openedWindows.length() > 0) {
modalWindow = openedWindows.top().value;//openedWindows栈顶的value值
modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);//modalWindow上的元素动画
}
}
function checkRemoveBackdrop() {
//remove backdrop if no longer needed
if (backdropDomEl && backdropIndex() == -1) {
var backdropScopeRef = backdropScope;
removeAfterAnimate(backdropDomEl, backdropScope, function() {
backdropScopeRef = null;
});
backdropDomEl = undefined;
backdropScope = undefined;
}
}
function removeAfterAnimate(domEl, scope, done) { //动画后移除dom
var asyncDeferred;
var asyncPromise = null;
var setIsAsync = function() { //生成一个promise对象
if (!asyncDeferred) {
asyncDeferred = $q.defer();
asyncPromise = asyncDeferred.promise;
}
return function asyncDone() {
asyncDeferred.resolve();
};
};
scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
// Note that it's intentional that asyncPromise might be null.
// That's when setIsAsync has not been called during the
// NOW_CLOSING_EVENT broadcast.
return $q.when(asyncPromise).then(afterAnimating);//执行
function afterAnimating() { //对应动画
if (afterAnimating.done) {
return;
}
afterAnimating.done = true;
if ($animateCss) {
$animateCss(domEl, {
event: 'leave'
}).start().then(function() {
domEl.remove();
});
} else {
$animate.leave(domEl);//angular自定义动画
}
scope.$destroy();
if (done) {
done();
}
}
}
$document.bind('keydown', function(evt) {//绑定keydow事件
if (evt.isDefaultPrevented()) { //判断是否调用e.preventDefault函数
return evt;
}
var modal = openedWindows.top(); //获取栈顶值
if (modal && modal.value.keyboard) {
switch (evt.which) {
case 27: {
evt.preventDefault();
$rootScope.$apply(function() {
$modalStack.dismiss(modal.key, 'escape key press');
});
break;
}
case 9: {
$modalStack.loadFocusElementList(modal);
var focusChanged = false;
if (evt.shiftKey) {
if ($modalStack.isFocusInFirstItem(evt)) {
focusChanged = $modalStack.focusLastFocusableElement();
}
} else {
if ($modalStack.isFocusInLastItem(evt)) {
focusChanged = $modalStack.focusFirstFocusableElement();
}
}
if (focusChanged) {
evt.preventDefault();
evt.stopPropagation();
}
break;
}
}
}
});
$modalStack.open = function(modalInstance, modal) { //创建一个模态框
var modalOpener = $document[0].activeElement, //获取有焦点的元素
modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; //获取类名
toggleTopWindowClass(false);//栈顶不触发
openedWindows.add(modalInstance, {//openedWindows注入keyvalue形式默认参数
deferred: modal.deferred,
renderDeferred: modal.renderDeferred,
modalScope: modal.scope,
backdrop: modal.backdrop,
keyboard: modal.keyboard,
openedClass: modal.openedClass,
windowTopClass: modal.windowTopClass
});
openedClasses.put(modalBodyClass, modalInstance); //openedClasses注入模态的类,和刚才注入的模态实例
var body = $document.find('body').eq(0), //获取body对象
currBackdropIndex = backdropIndex(); //获取有backdrop的属性值的索引
if (currBackdropIndex >= 0 && !backdropDomEl) {//如果存在
backdropScope = $rootScope.$new(true);//生成新的scope
backdropScope.index = currBackdropIndex;
var angularBackgroundDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');//生成背景angular元素
angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);//添加backdrop-class属性值,为modal.backdropClass
if (modal.animation) {//如果modal传入了animation
angularBackgroundDomEl.attr('modal-animation', 'true');//设置属性modal-animation为true
}
backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);//$compile编译angulardom元素,并且传入当前scope,生成dom元素
body.append(backdropDomEl);
}
var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');//模态窗
angularDomEl.attr({//设置属性
'template-url': modal.windowTemplateUrl,
'window-class': modal.windowClass,
'window-top-class': modal.windowTopClass,
'size': modal.size,
'index': openedWindows.length() - 1,
'animate': 'animate'
}).html(modal.content);
if (modal.animation) {
angularDomEl.attr('modal-animation', 'true');
}
var modalDomEl = $compile(angularDomEl)(modal.scope);
openedWindows.top().value.modalDomEl = modalDomEl;//获取openedWindows栈顶值的dom元素
openedWindows.top().value.modalOpener = modalOpener;
body.append(modalDomEl);//添加模态框
body.addClass(modalBodyClass);//添加类名
$modalStack.clearFocusListCache();
};
function broadcastClosing(modalWindow, resultOrReason, closing) {//向下广播事件modal.closing
return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;//preventDefault把defaultPrevented标志设置为true。尽管不能停止事件的传播,可以告诉子作用域无需处理这个事件
}
//消除模态框
$modalStack.close = function(modalInstance, result) {
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow && broadcastClosing(modalWindow, result, true)) {
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
modalWindow.value.deferred.resolve(result);//成功执行promise
removeModalWindow(modalInstance, modalWindow.value.modalOpener);
return true;
}
return !modalWindow;
};
//解除模态框
$modalStack.dismiss = function(modalInstance, reason) {
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
modalWindow.value.modalScope.$$uibDestructionScheduled = true;
modalWindow.value.deferred.reject(reason);//失败
removeModalWindow(modalInstance, modalWindow.value.modalOpener);
return true;
}
return !modalWindow;
};
$modalStack.dismissAll = function(reason) {//解除所有模态框
var topModal = this.getTop();
while (topModal && this.dismiss(topModal.key, reason)) {
topModal = this.getTop();
}
};
$modalStack.getTop = function() {//返回openedWindows栈顶
return openedWindows.top();
};
$modalStack.modalRendered = function(modalInstance) {//重新渲染
var modalWindow = openedWindows.get(modalInstance);
if (modalWindow) {
modalWindow.value.renderDeferred.resolve();
}
};
$modalStack.focusFirstFocusableElement = function() {//获取第一个焦点值
if (focusableElementList.length > 0) {
focusableElementList[0].focus();
return true;
}
return false;
};
$modalStack.focusLastFocusableElement = function() {//获取最后一个焦点值
if (focusableElementList.length > 0) {
focusableElementList[focusableElementList.length - 1].focus();
return true;
}
return false;
};
$modalStack.isFocusInFirstItem = function(evt) {//判断第一个是否获取焦点
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) == focusableElementList[0];
}
return false;
};
$modalStack.isFocusInLastItem = function(evt) {//判断最后一个是否获取焦点
if (focusableElementList.length > 0) {
return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1];
}
return false;
};
$modalStack.clearFocusListCache = function() {//清空焦点
focusableElementList = [];
focusIndex = 0;
};
$modalStack.loadFocusElementList = function(modalWindow) {//获取所有焦点的对象
if (focusableElementList === undefined || !focusableElementList.length) {
if (modalWindow) {
var modalDomE1 = modalWindow.value.modalDomEl;
if (modalDomE1 && modalDomE1.length) {
focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
}
}
}
};
return $modalStack;
}])
.provider('$uibModal', function() {
var $modalProvider = {
options: {
animation: true,
backdrop: true, //can also be false or 'static'
keyboard: true
},
$get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log',
function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) {
var $modal = {};
function getTemplatePromise(options) {//
return options.template ? $q.when(options.template) :
$templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl);//$templateRequest通过http下载模板,并存储在templateCache缓存中
}
function getResolvePromises(resolves) {//获取resolvespromise对象
var promisesArr = [];
angular.forEach(resolves, function(value) {
if (angular.isFunction(value) || angular.isArray(value)) {
promisesArr.push($q.when($injector.invoke(value)));//$q.when()把对象封装成promise对象
} else if (angular.isString(value)) {
promisesArr.push($q.when($injector.get(value)));
} else {
promisesArr.push($q.when(value));
}
});
return promisesArr;
}
var promiseChain = null;//返回promise链
$modal.getPromiseChain = function() {
return promiseChain;
};
$modal.open = function(modalOptions) {
var modalResultDeferred = $q.defer();//获取延迟对象
var modalOpenedDeferred = $q.defer();//获取延迟对象
var modalRenderDeferred = $q.defer();//获取延迟对象
//prepare an instance of a modal to be injected into controllers and returned to a caller
var modalInstance = {//模态实例
result: modalResultDeferred.promise,//获取promise对象
opened: modalOpenedDeferred.promise,//获取promise对象
rendered: modalRenderDeferred.promise,//获取promise对象
close: function (result) { //关闭实例
return $modalStack.close(modalInstance, result);
},
dismiss: function (reason) {//解除实例(即不可能完成promise)
return $modalStack.dismiss(modalInstance, reason);
}
};
//merge and clean up options
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);//把传入的modalOptions同$modalProvider.options进行扩展
modalOptions.resolve = modalOptions.resolve || {};
//verify options
if (!modalOptions.template && !modalOptions.templateUrl) {//当templateUrl并且template不存在抛出错误
throw new Error('One of template or templateUrl options is required.');
}
var templateAndResolvePromise =
$q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));//参数接收为一个promise数组,返回一个新的单一promise对象,当这些promise对象对应defer对象全部解决这个单一promise对象才会解决
function resolveWithTemplate() {
return templateAndResolvePromise;
}
// Wait for the resolution of the existing promise chain.
// Then switch to our own combined promise dependency (regardless of how the previous modal fared).
// Then add to $modalStack and resolve opened.
// Finally clean up the chain variable if no subsequent modal has overwritten it.
var samePromise;
samePromise = promiseChain = $q.all([promiseChain])
.then(resolveWithTemplate, resolveWithTemplate)
.then(function resolveSuccess(tplAndVars) {//成功resolve
var modalScope = (modalOptions.scope || $rootScope).$new();//生成一个新的scope
modalScope.$close = modalInstance.close;
modalScope.$dismiss = modalInstance.dismiss;
modalScope.$on('$destroy', function() {//modalScope监听$destroy
if (!modalScope.$$uibDestructionScheduled) {
modalScope.$dismiss('$uibUnscheduledDestruction');
}
});
var ctrlInstance, ctrlLocals = {};
var resolveIter = 1;
//controllers
if (modalOptions.controller) {
ctrlLocals.$scope = modalScope;
ctrlLocals.$uibModalInstance = modalInstance;
Object.defineProperty(ctrlLocals, '$modalInstance', {//设置属性Object.defineProperty(对象名,属性名,属性)
get: function() {
if (!$modalSuppressWarning) {
$log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.');
}
return modalInstance;
}
});
angular.forEach(modalOptions.resolve, function(value, key) {
ctrlLocals[key] = tplAndVars[resolveIter++];//把resolve植入ctrlLocals
});
ctrlInstance = $controller(modalOptions.controller, ctrlLocals);//加载控制器并传入一个作用域$controller
if (modalOptions.controllerAs) {
if (modalOptions.bindToController) {
angular.extend(ctrlInstance, modalScope);//把modalScope扩展到ctrlInstance实例上
}
modalScope[modalOptions.controllerAs] = ctrlInstance;
}
}
$modalStack.open(modalInstance, {//生成模态框
scope: modalScope,
deferred: modalResultDeferred,
renderDeferred: modalRenderDeferred,
content: tplAndVars[0],
animation: modalOptions.animation,
backdrop: modalOptions.backdrop,
keyboard: modalOptions.keyboard,
backdropClass: modalOptions.backdropClass,
windowTopClass: modalOptions.windowTopClass,
windowClass: modalOptions.windowClass,
windowTemplateUrl: modalOptions.windowTemplateUrl,
size: modalOptions.size,
openedClass: modalOptions.openedClass
});
modalOpenedDeferred.resolve(true);
}, function resolveError(reason) {
modalOpenedDeferred.reject(reason);
modalResultDeferred.reject(reason);
})
.finally(function() {
if (promiseChain === samePromise) {
promiseChain = null;
}
});
return modalInstance;
};
return $modal;
}
]
};
return $modalProvider;
});
/* deprecated modal below */
angular.module('ui.bootstrap.modal')
.value('$modalSuppressWarning', false)
/**
* A helper directive for the $modal service. It creates a backdrop element.
*/
.directive('modalBackdrop', [
'$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning',
function($animate , $injector, $modalStack, $log, $modalSuppressWarning) {
var $animateCss = null;
if ($injector.has('$animateCss')) {
$animateCss = $injector.get('$animateCss');
}
return {
replace: true,
templateUrl: 'template/modal/backdrop.html',
compile: function(tElement, tAttrs) {
tElement.addClass(tAttrs.backdropClass);
return linkFn;
}
};
function linkFn(scope, element, attrs) {
if (!$modalSuppressWarning) {
$log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.');
}
element.addClass('modal-backdrop');
if (attrs.modalInClass) {
if ($animateCss) {
$animateCss(element, {
addClass: attrs.modalInClass
}).start();
} else {
$animate.addClass(element, attrs.modalInClass);
}
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
var done = setIsAsync();
if ($animateCss) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
$animate.removeClass(element, attrs.modalInClass).then(done);
}
});
}
}
}])
.directive('modalWindow', [
'$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning',
function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) {
var $animateCss = null;
if ($injector.has('$animateCss')) {
$animateCss = $injector.get('$animateCss');
}
return {
scope: {
index: '@'
},
replace: true,
transclude: true,
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl || 'template/modal/window.html';
},
link: function(scope, element, attrs) {
if (!$modalSuppressWarning) {
$log.warn('modal-window is now deprecated. Use uib-modal-window instead.');
}
element.addClass(attrs.windowClass || '');
element.addClass(attrs.windowTopClass || '');
scope.size = attrs.size;
scope.close = function(evt) {
var modal = $modalStack.getTop();
if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
evt.preventDefault();
evt.stopPropagation();
$modalStack.dismiss(modal.key, 'backdrop click');
}
};
// moved from template to fix issue #2280
element.on('click', scope.close);
// This property is only added to the scope for the purpose of detecting when this directive is rendered.
// We can detect that by using this property in the template associated with this directive and then use
// {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
scope.$isRendered = true;
// Deferred object that will be resolved when this modal is render.
var modalRenderDeferObj = $q.defer();
// Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
// In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
attrs.$observe('modalRender', function(value) {
if (value == 'true') {
modalRenderDeferObj.resolve();
}
});
modalRenderDeferObj.promise.then(function() {
var animationPromise = null;
if (attrs.modalInClass) {
if ($animateCss) {
animationPromise = $animateCss(element, {
addClass: attrs.modalInClass
}).start();
} else {
animationPromise = $animate.addClass(element, attrs.modalInClass);
}
scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
var done = setIsAsync();
if ($animateCss) {
$animateCss(element, {
removeClass: attrs.modalInClass
}).start().then(done);
} else {
$animate.removeClass(element, attrs.modalInClass).then(done);
}
});
}
$q.when(animationPromise).then(function() {
var inputWithAutofocus = element[0].querySelector('[autofocus]');
/**
* Auto-focusing of a freshly-opened modal element causes any child elements
* with the autofocus attribute to lose focus. This is an issue on touch
* based devices which will show and then hide the onscreen keyboard.
* Attempts to refocus the autofocus element via JavaScript will not reopen
* the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
* the modal element if the modal does not contain an autofocus element.
*/
if (inputWithAutofocus) {
inputWithAutofocus.focus();
} else {
element[0].focus();
}
});
// Notify {@link $modalStack} that modal is rendered.
var modal = $modalStack.getTop();
if (modal) {
$modalStack.modalRendered(modal.key);
}
});
}
};
}])
.directive('modalAnimationClass', [
'$log', '$modalSuppressWarning',
function ($log, $modalSuppressWarning) {
return {
compile: function(tElement, tAttrs) {
if (!$modalSuppressWarning) {
$log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.');
}
if (tAttrs.modalAnimation) {
tElement.addClass(tAttrs.modalAnimationClass);
}
}
};
}])
.directive('modalTransclude', [
'$log', '$modalSuppressWarning',
function ($log, $modalSuppressWarning) {
return {
link: function($scope, $element, $attrs, controller, $transclude) {
if (!$modalSuppressWarning) {
$log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.');
}
$transclude($scope.$parent, function(clone) {
$element.empty();
$element.append(clone);
});
}
};
}])
.service('$modalStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
'$$multiMap',
'$$stackedMap',
'$uibModalStack',
'$log',
'$modalSuppressWarning',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector,
$$multiMap,
$$stackedMap,
$uibModalStack,
$log,
$modalSuppressWarning) {
if (!$modalSuppressWarning) {
$log.warn('$modalStack is now deprecated. Use $uibModalStack instead.');
}
angular.extend(this, $uibModalStack);//把$uibModalStack扩展到$modalStack上
}])
.provider('$modal', ['$uibModalProvider', function($uibModalProvider) {
angular.extend(this, $uibModalProvider);
this.$get = ['$injector', '$log', '$modalSuppressWarning',
function ($injector, $log, $modalSuppressWarning) {
if (!$modalSuppressWarning) {
$log.warn('$modal is now deprecated. Use instead.');
}
return $injector.invoke($uibModalProvider.$get);//通过$uibModal服务的服务提供商来获取get注入到服务中
}];
}]);
angular.module('ui.bootstrap.stackedMap', [])
/**
* A helper, internal data structure that acts as a map but also allows getting / removing
* elements in the LIFO order
*/
.factory('$$stackedMap', function() {//$$stackedMap map数据结构,并且满足先进先出
return {
createNew: function() {
var stack = [];//数组对象
return {
add: function(key, value) {//增加一对key-value值
stack.push({
key: key,
value: value
});
},
get: function(key) {//获取一对key-value值
for (var i = 0; i < stack.length; i++) {
if (key == stack[i].key) {
return stack[i];
}
}
},
keys: function() {//返回所有key的集合
var keys = [];
for (var i = 0; i < stack.length; i++) {
keys.push(stack[i].key);
}
return keys;
},
top: function() {//返回栈顶的key-value
return stack[stack.length - 1];
},
remove: function(key) {//移除对应的key-value
var idx = -1;
for (var i = 0; i < stack.length; i++) {
if (key == stack[i].key) {
idx = i;
break;
}
}
return stack.splice(idx, 1)[0];
},
removeTop: function() {//移除栈顶
return stack.splice(stack.length - 1, 1)[0];
},
length: function() {//返回长度
return stack.length;
}
};
}
};
});
angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/modal/backdrop.html",
"<div uib-modal-animation-class=\"fade\"\n" +
" modal-in-class=\"in\"\n" +
" ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
"></div>\n" +
"");
}]);
angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
$templateCache.put("template/modal/window.html",
"<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
" uib-modal-animation-class=\"fade\"\n" +
" modal-in-class=\"in\"\n" +
" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
" <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
"</div>\n" +
"");
}]);
```
从源码看,里面好多代码有很大的重复性,大家可以自己看一下,我就不再追溯了。
整个插件的基本思路就是,在插件调用的时候,先通过下面的方式进行参数传递,生成模态的背景和窗体
```
angular.module('ui.bootstrap.demo').controller('ModalDemoCtrl', function ($scope, $uibModal, $log) {
$scope.items = ['item1', 'item2', 'item3'];
$scope.animationsEnabled = true;
$scope.open = function (size) {
var modalInstance = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
modalInstance.result.then(function (selectedItem) {
$scope.selected = selectedItem;
}, function () {
$log.info('Modal dismissed at: ' + new Date());
});
};
$scope.toggleAnimation = function () {
$scope.animationsEnabled = !$scope.animationsEnabled;
};
});
```
参数传递就是里面$uibModal.open({options})里面的参数,参数传入$uibModal.open后会与其内部默认的参数进行扩展,形成一个新的参数数组,扩展代码是
```
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
```
然后在加上生成的延迟对象,一起加入到$uibModalStack服务中$modalStack.open函数中
```
$modalStack.open(modalInstance, {/
scope: modalScope,
deferred: modalResultDeferred,
renderDeferred: modalRenderDeferred,
content: tplAndVars[0],
animation: modalOptions.animation,
backdrop: modalOptions.backdrop,
keyboard: modalOptions.keyboard,
backdropClass: modalOptions.backdropClass,
windowTopClass: modalOptions.windowTopClass,
windowClass: modalOptions.windowClass,
windowTemplateUrl: modalOptions.windowTemplateUrl,
size: modalOptions.size,
openedClass: modalOptions.openedClass
});
```
这样做的目的是,因为在$uibModalStack服务中可以把这些变量添加到一个map结构数组中,因为在$uibModalStack中实例化了map数据结构,可以调用其add方法来添加
```
$modalStack.open = function(modalInstance, modal) { //创建一个模态框
var modalOpener = $document[0].activeElement, //获取有焦点的元素
modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; //获取类名
toggleTopWindowClass(false);//栈顶不触发
openedWindows.add(modalInstance, {//openedWindows注入keyvalue形式默认参数
deferred: modal.deferred,
renderDeferred: modal.renderDeferred,
modalScope: modal.scope,
backdrop: modal.backdrop,
keyboard: modal.keyboard,
openedClass: modal.openedClass,
windowTopClass: modal.windowTopClass
});
openedClasses.put(modalBodyClass, modalInstance); //openedClasses注入模态的类,和刚才注入的模态实例
var body = $document.find('body').eq(0), //获取body对象
currBackdropIndex = backdropIndex(); //获取有backdrop的属性值的索引
if (currBackdropIndex >= 0 && !backdropDomEl) {//如果存在
backdropScope = $rootScope.$new(true);//生成新的scope
backdropScope.index = currBackdropIndex;
var angularBackgroundDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');//生成背景angular元素
angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);//添加backdrop-class属性值,为modal.backdropClass
if (modal.animation) {//如果modal传入了animation
angularBackgroundDomEl.attr('modal-animation', 'true');//设置属性modal-animation为true
}
backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);//$compile编译angulardom元素,并且传入当前scope,生成dom元素
body.append(backdropDomEl);
}
var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');//模态窗
angularDomEl.attr({//设置属性
'template-url': modal.windowTemplateUrl,
'window-class': modal.windowClass,
'window-top-class': modal.windowTopClass,
'size': modal.size,
'index': openedWindows.length() - 1,
'animate': 'animate'
}).html(modal.content);
if (modal.animation) {
angularDomEl.attr('modal-animation', 'true');
}
var modalDomEl = $compile(angularDomEl)(modal.scope);
openedWindows.top().value.modalDomEl = modalDomEl;//获取openedWindows栈顶值的dom元素
openedWindows.top().value.modalOpener = modalOpener;
body.append(modalDomEl);//添加模态框
body.addClass(modalBodyClass);//添加类名
$modalStack.clearFocusListCache();
};
```
可以看到内部有openedWindows.add方法可以把参数都植入到这个数据结构中,方便进行调用,在调用open的时候,动态生成了背景和模态窗口。
然后就是指令,把$uibModalStack服务传入指令uibModalWindow中,这样就可以通过element和attr来调用templateUrl中的属性来添加和消除动画