angularjs中UI插件ui-bootstrap中modal模態(tài)彈出源碼分析

最近看了一下UI-Bootstrap的源碼,感覺(jué)寫(xiě)的非常好,對(duì)自己寫(xiě)angular程序有很大的啟發(fā),源碼指令也很高,這里先簡(jiǎn)單分析一下,然后把思路整理一下,如果有錯(cuò)誤歡迎指出來(lái)。
我用思維導(dǎo)圖做了一個(gè)模塊依賴關(guān)系,可以結(jié)合看一下,以下是連接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() {//生成一個(gè)map數(shù)據(jù)結(jié)構(gòu)用來(lái)保存
    return {
      createNew: function() {
        var map = {};//內(nèi)部數(shù)據(jù),聲明周期是整個(gè)app的聲明周期,用來(lái)保存數(shù)據(jù)

        return {
          entries: function() {//生成一個(gè)map數(shù)據(jù)結(jié)構(gòu)
            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上對(duì)應(yīng)key的value
            return map[key];
          },
          hasKey: function(key) {//判斷map數(shù)據(jù)結(jié)構(gòu)上是否有key
            return !!map[key]; //把!!強(qiáng)制轉(zhuǎn)換為布爾類型
          },
          keys: function() { //返回map數(shù)據(jù)結(jié)構(gòu)上的所有屬性和方法
            return Object.keys(map);
          },
          put: function(key, value) {//把key-value放入map
            if (!map[key]) {
              map[key] = []; //一個(gè)key可以對(duì)應(yīng)多個(gè)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是否注入到服務(wù)中
      $animateCss = $injector.get('$animateCss');//如果有獲取
    }

    return {
      replace: true,
      templateUrl: 'template/modal/backdrop.html',
      compile: function(tElement, tAttrs) {
        tElement.addClass(tAttrs.backdropClass); //為element添加backdrop-class屬性對(duì)應(yīng)的類名
        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, {//調(diào)用函數(shù)
            addClass: attrs.modalInClass
          }).start();
        } else {
          $animate.addClass(element, attrs.modalInClass);// 使用angular自定義動(dòng)畫(huà),添加屬性集合中modal-in-class對(duì)應(yīng)的類
        }

        scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {//監(jiān)聽(tīng)$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自定義動(dòng)畫(huà),添加屬性集合中modal-in-class對(duì)應(yīng)的類
          }
        });
      }
    }
  }])

  .directive('uibModalWindow', [//模態(tài)窗口指令
           '$uibModalStack', '$q', '$animate', '$injector',
  function($modalStack ,  $q ,  $animate,   $injector) {
    var $animateCss = null;

    if ($injector.has('$animateCss')) {//判斷是否注入了$animateCss這個(gè)服務(wù)
      $animateCss = $injector.get('$animateCss');//如果有就獲取
    }

    return {
      scope: {
        index: '@'//同父scope的index屬性進(jìn)行綁定
      },
      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屬性對(duì)應(yīng)的類,就是in
        element.addClass(attrs.windowTopClass || '');//為element添加屬性集合attrs中window-top-class屬性對(duì)應(yīng)的類
        scope.size = attrs.size;

        scope.close = function(evt) { //關(guān)閉方法
          var modal = $modalStack.getTop(); //獲取$uibModalStack頂部棧
          if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
            evt.preventDefault(); //阻止默認(rèn)方法
            evt.stopPropagation();//阻止事件冒泡
            $modalStack.dismiss(modal.key, 'backdrop click');//調(diào)用$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();//獲取一個(gè)異步對(duì)象
        // 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) {//同上都是來(lái)判斷用什么動(dòng)畫(huà)方法
            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(); //獲取焦點(diǎn)
            } 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鏈接函數(shù)是實(shí)際被執(zhí)行用來(lái)克隆元素和操作DOM的函數(shù)
        $transclude($scope.$parent, function(clone) {
          $element.empty(); //清空element
          $element.append(clone);//添加參數(shù)元素,這里用來(lái)向element添加元素
        });
      }
    };
  })

  .factory('$uibModalStack', [ //$uibModalStack服務(wù)
             '$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動(dòng)畫(huà)
        $animateCss = $injector.get('$animateCss');//獲取
      }

      var OPENED_MODAL_CLASS = 'modal-open';

      var backdropDomEl, backdropScope;
      var openedWindows = $$stackedMap.createNew();//$$stackedMap實(shí)例
      var openedClasses = $$multiMap.createNew();//$$multiMap實(shí)例
      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]), ' + //多去焦點(diǎn)的元素
        'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
        'iframe, object, embed, *[tabindex], *[contenteditable=true]';

      function backdropIndex() { //設(shè)置背景z-index值
        var topBackdropIndex = -1;
        var opened = openedWindows.keys(); //獲取openedWindows中map對(duì)象所有的屬性和方法
        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) { //監(jiān)聽(tīng)backdropIndex,當(dāng)有新的值的時(shí)候backdropScope.index等于新值
        if (backdropScope) {
          backdropScope.index = newBackdropIndex;
        }
      });

      function removeModalWindow(modalInstance, elementToReceiveFocus) { //移除模態(tài)窗口
        var body = $document.find('body').eq(0);//獲取body對(duì)象
        var modalWindow = openedWindows.get(modalInstance).value; //獲取openedWindows中modalInstance對(duì)應(yīng)的實(shí)例的值

        //clean up the stack
        openedWindows.remove(modalInstance);//從openedWindows這個(gè)數(shù)據(jù)結(jié)構(gòu)中移除modalInstance這個(gè)對(duì)象

        removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { //移除后動(dòng)畫(huà)函數(shù)
          var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; //value值中openedClass的屬性值
          openedClasses.remove(modalBodyClass, modalInstance);//另一個(gè)map結(jié)構(gòu)也移除
          body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));//body toggleClass動(dòng)畫(huà)
          toggleTopWindowClass(true);
        });
        checkRemoveBackdrop();

        //move focus to specified element if available, or else to body
        if (elementToReceiveFocus && elementToReceiveFocus.focus) { //獲取焦點(diǎn)
          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棧頂?shù)膙alue值
          modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);//modalWindow上的元素動(dòng)畫(huà)
        }
      }

      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) { //動(dòng)畫(huà)后移除dom
        var asyncDeferred;
        var asyncPromise = null;
        var setIsAsync = function() { //生成一個(gè)promise對(duì)象
          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);//執(zhí)行

        function afterAnimating() { //對(duì)應(yīng)動(dòng)畫(huà)
          if (afterAnimating.done) {
            return;
          }
          afterAnimating.done = true;

          if ($animateCss) {
            $animateCss(domEl, {
              event: 'leave'
            }).start().then(function() {
              domEl.remove();
            });
          } else {
            $animate.leave(domEl);//angular自定義動(dòng)畫(huà)
          }
          scope.$destroy();
          if (done) {
            done();
          }
        }
      }

      $document.bind('keydown', function(evt) {//綁定keydow事件
        if (evt.isDefaultPrevented()) { //判斷是否調(diào)用e.preventDefault函數(shù)
          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) { //創(chuàng)建一個(gè)模態(tài)框
        var modalOpener = $document[0].activeElement, //獲取有焦點(diǎn)的元素
          modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; //獲取類名

        toggleTopWindowClass(false);//棧頂不觸發(fā)

        openedWindows.add(modalInstance, {//openedWindows注入keyvalue形式默認(rèn)參數(shù)
          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注入模態(tài)的類,和剛才注入的模態(tài)實(shí)例

        var body = $document.find('body').eq(0), //獲取body對(duì)象
            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');//設(shè)置屬性modal-animation為true
          }
          backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);//$compile編譯angulardom元素,并且傳入當(dāng)前scope,生成dom元素
          body.append(backdropDomEl);
        }

        var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');//模態(tài)窗
        angularDomEl.attr({//設(shè)置屬性
          '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);//添加模態(tài)框
        body.addClass(modalBodyClass);//添加類名

        $modalStack.clearFocusListCache();
      };

      function broadcastClosing(modalWindow, resultOrReason, closing) {//向下廣播事件modal.closing
        return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;//preventDefault把defaultPrevented標(biāo)志設(shè)置為true。盡管不能停止事件的傳播,可以告訴子作用域無(wú)需處理這個(gè)事件
      }
    //消除模態(tài)框
      $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);//成功執(zhí)行promise
          removeModalWindow(modalInstance, modalWindow.value.modalOpener);
          return true;
        }
        return !modalWindow;
      };
    //解除模態(tài)框
      $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) {//解除所有模態(tài)框
        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() {//獲取第一個(gè)焦點(diǎn)值
        if (focusableElementList.length > 0) {
          focusableElementList[0].focus();
          return true;
        }
        return false;
      };
      $modalStack.focusLastFocusableElement = function() {//獲取最后一個(gè)焦點(diǎn)值
        if (focusableElementList.length > 0) {
          focusableElementList[focusableElementList.length - 1].focus();
          return true;
        }
        return false;
      };

      $modalStack.isFocusInFirstItem = function(evt) {//判斷第一個(gè)是否獲取焦點(diǎn)
        if (focusableElementList.length > 0) {
          return (evt.target || evt.srcElement) == focusableElementList[0];
        }
        return false;
      };

      $modalStack.isFocusInLastItem = function(evt) {//判斷最后一個(gè)是否獲取焦點(diǎn)
        if (focusableElementList.length > 0) {
          return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1];
        }
        return false;
      };

      $modalStack.clearFocusListCache = function() {//清空焦點(diǎn)
        focusableElementList = [];
        focusIndex = 0;
      };

      $modalStack.loadFocusElementList = function(modalWindow) {//獲取所有焦點(diǎn)的對(duì)象
        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通過(guò)http下載模板,并存儲(chǔ)在templateCache緩存中
          }

          function getResolvePromises(resolves) {//獲取resolvespromise對(duì)象
            var promisesArr = [];
            angular.forEach(resolves, function(value) {
              if (angular.isFunction(value) || angular.isArray(value)) {
                promisesArr.push($q.when($injector.invoke(value)));//$q.when()把對(duì)象封裝成promise對(duì)象
              } 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();//獲取延遲對(duì)象
            var modalOpenedDeferred = $q.defer();//獲取延遲對(duì)象
            var modalRenderDeferred = $q.defer();//獲取延遲對(duì)象

            //prepare an instance of a modal to be injected into controllers and returned to a caller
            var modalInstance = {//模態(tài)實(shí)例
              result: modalResultDeferred.promise,//獲取promise對(duì)象
              opened: modalOpenedDeferred.promise,//獲取promise對(duì)象
              rendered: modalRenderDeferred.promise,//獲取promise對(duì)象
              close: function (result) { //關(guān)閉實(shí)例
                return $modalStack.close(modalInstance, result);
              },
              dismiss: function (reason) {//解除實(shí)例(即不可能完成promise)
                return $modalStack.dismiss(modalInstance, reason);
              }
            };

            //merge and clean up options
            modalOptions = angular.extend({}, $modalProvider.options, modalOptions);//把傳入的modalOptions同$modalProvider.options進(jìn)行擴(kuò)展
            modalOptions.resolve = modalOptions.resolve || {};

            //verify options
            if (!modalOptions.template && !modalOptions.templateUrl) {//當(dāng)templateUrl并且template不存在拋出錯(cuò)誤
              throw new Error('One of template or templateUrl options is required.');
            }

            var templateAndResolvePromise =
              $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));//參數(shù)接收為一個(gè)promise數(shù)組,返回一個(gè)新的單一promise對(duì)象,當(dāng)這些promise對(duì)象對(duì)應(yīng)defer對(duì)象全部解決這個(gè)單一promise對(duì)象才會(huì)解決

            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();//生成一個(gè)新的scope
                modalScope.$close = modalInstance.close;
                modalScope.$dismiss = modalInstance.dismiss;

                modalScope.$on('$destroy', function() {//modalScope監(jiān)聽(tīng)$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', {//設(shè)置屬性O(shè)bject.defineProperty(對(duì)象名,屬性名,屬性)
                    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);//加載控制器并傳入一個(gè)作用域$controller
                  if (modalOptions.controllerAs) { 
                    if (modalOptions.bindToController) {
                      angular.extend(ctrlInstance, modalScope);//把modalScope擴(kuò)展到ctrlInstance實(shí)例上
                    }

                    modalScope[modalOptions.controllerAs] = ctrlInstance;
                  }
                }

                $modalStack.open(modalInstance, {//生成模態(tài)框
                  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擴(kuò)展到$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);//通過(guò)$uibModal服務(wù)的服務(wù)提供商來(lái)獲取get注入到服務(wù)中
      }];
  }]);

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數(shù)據(jù)結(jié)構(gòu),并且滿足先進(jìn)先出
    return {
      createNew: function() {
        var stack = [];//數(shù)組對(duì)象

        return {
          add: function(key, value) {//增加一對(duì)key-value值
            stack.push({
              key: key,
              value: value
            });
          },
          get: function(key) {//獲取一對(duì)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() {//返回棧頂?shù)膋ey-value
            return stack[stack.length - 1];
          },
          remove: function(key) {//移除對(duì)應(yīng)的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() {//返回長(zhǎng)度
            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" +
    "");
}]);
```
從源碼看,里面好多代碼有很大的重復(fù)性,大家可以自己看一下,我就不再追溯了。
整個(gè)插件的基本思路就是,在插件調(diào)用的時(shí)候,先通過(guò)下面的方式進(jìn)行參數(shù)傳遞,生成模態(tài)的背景和窗體
```
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;
        };
    });
```
參數(shù)傳遞就是里面$uibModal.open({options})里面的參數(shù),參數(shù)傳入$uibModal.open后會(huì)與其內(nèi)部默認(rèn)的參數(shù)進(jìn)行擴(kuò)展,形成一個(gè)新的參數(shù)數(shù)組,擴(kuò)展代碼是
```
modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
```
然后在加上生成的延遲對(duì)象,一起加入到$uibModalStack服務(wù)中$modalStack.open函數(shù)中
```
$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
 });
```
這樣做的目的是,因?yàn)樵?uibModalStack服務(wù)中可以把這些變量添加到一個(gè)map結(jié)構(gòu)數(shù)組中,因?yàn)樵?uibModalStack中實(shí)例化了map數(shù)據(jù)結(jié)構(gòu),可以調(diào)用其add方法來(lái)添加
```
    $modalStack.open = function(modalInstance, modal) { //創(chuàng)建一個(gè)模態(tài)框
        var modalOpener = $document[0].activeElement, //獲取有焦點(diǎn)的元素
          modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; //獲取類名
        toggleTopWindowClass(false);//棧頂不觸發(fā)
        openedWindows.add(modalInstance, {//openedWindows注入keyvalue形式默認(rèn)參數(shù)
          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注入模態(tài)的類,和剛才注入的模態(tài)實(shí)例
        var body = $document.find('body').eq(0), //獲取body對(duì)象
            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');//設(shè)置屬性modal-animation為true
          }
          backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);//$compile編譯angulardom元素,并且傳入當(dāng)前scope,生成dom元素
          body.append(backdropDomEl);
        }
        var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');//模態(tài)窗
        angularDomEl.attr({//設(shè)置屬性
          '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);//添加模態(tài)框
        body.addClass(modalBodyClass);//添加類名
        $modalStack.clearFocusListCache();
  };
```
可以看到內(nèi)部有openedWindows.add方法可以把參數(shù)都植入到這個(gè)數(shù)據(jù)結(jié)構(gòu)中,方便進(jìn)行調(diào)用,在調(diào)用open的時(shí)候,動(dòng)態(tài)生成了背景和模態(tài)窗口。
然后就是指令,把$uibModalStack服務(wù)傳入指令uibModalWindow中,這樣就可以通過(guò)element和attr來(lái)調(diào)用templateUrl中的屬性來(lái)添加和消除動(dòng)畫(huà)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,532評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,991評(píng)論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,778評(píng)論 18 399
  • 母親的家在蘇北古黃河道岸邊,黃河改道,留下一個(gè)高高的坡。黃色的土,天干時(shí)細(xì)如沙如粉,雨后粘粘連連像和出的稀泥。這里...
    瓦房聽(tīng)雨閱讀 477評(píng)論 12 22
  • 地點(diǎn):繁花谷 清晨霧氣彌漫,谷底上各式奇異的花朵沾染著露珠鮮艷綻放。一個(gè)少女一身淺藍(lán)色的齊膝短裙,步伐輕盈...
    霧緣林閱讀 167評(píng)論 0 0