美文网首页AngularJs我爱编程
angularjs中UI插件ui-bootstrap中modal

angularjs中UI插件ui-bootstrap中modal

作者: MakingChoice | 来源:发表于2015-12-01 20:59 被阅读13960次

    最近看了一下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中的属性来添加和消除动画

    相关文章

      网友评论

      本文标题:angularjs中UI插件ui-bootstrap中modal

      本文链接:https://www.haomeiwen.com/subject/bxwjhttx.html