• element-ui MessageBox组件源码分析整理笔记(十二)


    MessageBox组件源码,有添加部分注释

    main.vue

    <template>
      <transition name="msgbox-fade">
        <!--包裹弹框的div-->
        <div
          class="el-message-box__wrapper"
          tabindex="-1"
          v-show="visible"
          @click.self="handleWrapperClick"
          role="dialog"
          aria-modal="true"
          :aria-label="title || 'dialog'">
          <!--中间的弹框-->
          <div class="el-message-box" :class="[customClass, center && 'el-message-box--center']">
            <!--弹窗头部,包含:标题和关闭按钮;title必须设置,如果不设置不显示头部信息-->
            <div class="el-message-box__header" v-if="title !== null">
              <!--头部标题-->
              <div class="el-message-box__title">
                <!--center为true时,为居中布局,可设置图标,图标和标题居中显示-->
                <div
                  :class="['el-message-box__status', icon]"
                  v-if="icon && center">
                </div>
                <span>{{ title }}</span>
              </div>
              <!--头部关闭按钮-->
              <button
                type="button"
                class="el-message-box__headerbtn"
                aria-label="Close"
                v-if="showClose"
                @click="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')"
                @keydown.enter="handleAction(distinguishCancelAndClose ? 'close' : 'cancel')">
                <i class="el-message-box__close el-icon-close"></i>
              </button>
            </div>
            <!--弹框内容部分-->
            <div class="el-message-box__content">
                <!--消息类型的图标-->
              <div
                :class="['el-message-box__status', icon]"
                v-if="icon && !center && message !== ''">
              </div>
                <!--弹框的主要内容-->
              <div class="el-message-box__message" v-if="message !== ''">
                <slot>
                  <!--dangerouslyUseHTMLString是否将 message 属性作为 HTML 片段处理,如果该字段不存在时,直接显示message-->
                  <p v-if="!dangerouslyUseHTMLString">{{ message }}</p>
                  <!--如果存在将message作为HTML处理-->
                  <p v-else v-html="message"></p>
                </slot>
              </div>
              <!--输入框部分,根据设置的showInput显示-->
              <div class="el-message-box__input" v-show="showInput">
                <el-input
                  v-model="inputValue"
                  :type="inputType"
                  @keydown.enter.native="handleInputEnter"
                  :placeholder="inputPlaceholder"
                  ref="input">
                </el-input>
                 <!--检验错误的提示信息-->
                <div class="el-message-box__errormsg" :style="{ visibility: !!editorErrorMessage ? 'visible' : 'hidden' }">{{ editorErrorMessage }}</div>
              </div>
            </div>
            <!--弹框底部,包含:确认、取消按钮-->
            <div class="el-message-box__btns">
              <el-button
                :loading="cancelButtonLoading"
                :class="[ cancelButtonClasses ]"
                v-if="showCancelButton"
                :round="roundButton"
                size="small"
                @click.native="handleAction('cancel')"
                @keydown.enter="handleAction('cancel')">
                {{ cancelButtonText || t('el.messagebox.cancel') }}
              </el-button>
              <el-button
                :loading="confirmButtonLoading"
                ref="confirm"
                :class="[ confirmButtonClasses ]"
                v-show="showConfirmButton"
                :round="roundButton"
                size="small"
                @click.native="handleAction('confirm')"
                @keydown.enter="handleAction('confirm')">
                {{ confirmButtonText || t('el.messagebox.confirm') }}
              </el-button>
            </div>
          </div>
        </div>
      </transition>
    </template>
    
    <script type="text/babel">
      import Popup from 'element-ui/src/utils/popup';
      import Locale from 'element-ui/src/mixins/locale';
      import ElInput from 'element-ui/packages/input';
      import ElButton from 'element-ui/packages/button';
      import { addClass, removeClass } from 'element-ui/src/utils/dom';
      import { t } from 'element-ui/src/locale';
      import Dialog from 'element-ui/src/utils/aria-dialog';
    
      let messageBox;
      //定义的图标类型
      let typeMap = {
        success: 'success',
        info: 'info',
        warning: 'warning',
        error: 'error'
      };
    
      export default {
        mixins: [Popup, Locale],
    
        props: {
          modal: {
            default: true
          },
          lockScroll: { //是否在 MessageBox 出现时将 body 滚动锁定
            default: true
          },
          showClose: { //MessageBox 是否显示右上角关闭按钮
            type: Boolean,
            default: true
          },
          closeOnClickModal: { //是否可通过点击遮罩关闭 MessageBox
            default: true
          },
          closeOnPressEscape: { //是否可通过按下 ESC 键关闭 MessageBox
            default: true
          },
          closeOnHashChange: { //是否在 hashchange 时关闭 MessageBox
            default: true
          },
          center: { //是否居中布局
            default: false,
            type: Boolean
          },
          roundButton: { //是否使用圆角按钮
            default: false,
            type: Boolean
          }
        },
    
        components: {
          ElInput,
          ElButton
        },
    
        computed: {
          icon() {
            const { type, iconClass } = this;
            //如果用户设置了自定义图标的类名,就显示自定义图标;如果没有就显示设置的type图标,否则就不显示图标
            return iconClass || (type && typeMap[type] ? `el-icon-${ typeMap[type] }` : '');
          },
          //添加确定按钮的自定义类名
          confirmButtonClasses() {
            return `el-button--primary ${ this.confirmButtonClass }`;
          },
          //添加取消按钮的自定义类名
          cancelButtonClasses() {
            return `${ this.cancelButtonClass }`;
          }
        },
    
        methods: {
          getSafeClose() {
            const currentId = this.uid;
            return () => {
              this.$nextTick(() => {
                if (currentId === this.uid) this.doClose();
              });
            };
          },
          doClose() {
            if (!this.visible) return;
            this.visible = false;
            this._closing = true;
    
            this.onClose && this.onClose();
            messageBox.closeDialog(); // 解绑
            if (this.lockScroll) {
              setTimeout(this.restoreBodyStyle, 200);
            }
            this.opened = false;
            this.doAfterClose();
            setTimeout(() => {
              if (this.action) this.callback(this.action, this);
            });
          },
          //点击弹框时,根据closeOnClickModal来是否可通过点击遮罩关闭 MessageBox
          handleWrapperClick() {
            // 如果closeOnClickModal设置为true
            if (this.closeOnClickModal) {
              //判断是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分
              //如果区分则this.handleAction('close');否则this.handleAction('cancel');
              this.handleAction(this.distinguishCancelAndClose ? 'close' : 'cancel');
            }
          },
    
          handleInputEnter() {
            if (this.inputType !== 'textarea') {
              return this.handleAction('confirm');
            }
          },
    
          handleAction(action) {
            // 如果当前是this.$prompt
            if (this.$type === 'prompt' && action === 'confirm' && !this.validate()) {
              return;
            }
            this.action = action;
            //判断beforeClose是否是函数,也就是用户是否定义了beforeClose函数
            if (typeof this.beforeClose === 'function') {
              this.close = this.getSafeClose();
              this.beforeClose(action, this, this.close);
            } else {
              //如果用户没有定义beforeClose,就调doClose直接关闭弹框
              this.doClose();
            }
          },
          //该方法主要是用于用户在调用$prompt方法打开消息提示时,校验input输入框的值
          validate() {
            //$prompt方法即可打开消息提示,它模拟了系统的 prompt
            if (this.$type === 'prompt') {
              //获取用户自己定义的匹配模式
              const inputPattern = this.inputPattern;
              //如果用户自己定义了匹配模式,并且用户输入校验不通过
              if (inputPattern && !inputPattern.test(this.inputValue || '')) {
                //显示用户自己定义的校验不通过时的提示信息;当用户未定义校验不通过的提示信息时,t('el.messagebox.error')输出提示:输入的数据不合法!
                this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error');
                //这里主要是在校验不通过时,给input加上的类名中加上invalid,变成class="el-input__inner invalid"
                //通过.el-message-box__input input.invalid{border-color: #f56c6c;}改变input的border为红色
                addClass(this.getInputElement(), 'invalid');
                return false;
              }
              //输入框的校验函数;可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage
              const inputValidator = this.inputValidator;
              //如果校验函数存在
              if (typeof inputValidator === 'function') {
                const validateResult = inputValidator(this.inputValue);
                //校验不通过,显示校验不通过的红色提示信息
                if (validateResult === false) {
                  this.editorErrorMessage = this.inputErrorMessage || t('el.messagebox.error');
                  addClass(this.getInputElement(), 'invalid');
                  return false;
                }
                //若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage
                if (typeof validateResult === 'string') {
                  this.editorErrorMessage = validateResult;
                  addClass(this.getInputElement(), 'invalid');
                  return false;
                }
              }
            }
            //如果校验通过,则不显示错误提示,并删除类名invalid
            this.editorErrorMessage = '';
            removeClass(this.getInputElement(), 'invalid');
            return true;
          },
          getFirstFocus() {
            const btn = this.$el.querySelector('.el-message-box__btns .el-button');
            const title = this.$el.querySelector('.el-message-box__btns .el-message-box__title');
            return btn || title;
          },
          //获取input元素
          getInputElement() {
            const inputRefs = this.$refs.input.$refs;
            return inputRefs.input || inputRefs.textarea;
          }
        },
    
        watch: {
          inputValue: {
            immediate: true,
            handler(val) {
              this.$nextTick(_ => {
                if (this.$type === 'prompt' && val !== null) {
                  this.validate();
                }
              });
            }
          },
    
          visible(val) {
            if (val) {
              this.uid++;
              if (this.$type === 'alert' || this.$type === 'confirm') {
                this.$nextTick(() => {
                  this.$refs.confirm.$el.focus();
                });
              }
              this.focusAfterClosed = document.activeElement;
              messageBox = new Dialog(this.$el, this.focusAfterClosed, this.getFirstFocus());
            }
    
            // prompt
            if (this.$type !== 'prompt') return;
            if (val) {
              setTimeout(() => {
                if (this.$refs.input && this.$refs.input.$el) {
                  this.getInputElement().focus();
                }
              }, 500);
            } else {
              this.editorErrorMessage = '';
              removeClass(this.getInputElement(), 'invalid');
            }
          }
        },
    
        mounted() {
          this.$nextTick(() => {
            //根据设置的closeOnHashChange参数判断是否在 hashchange 时关闭 MessageBox
            if (this.closeOnHashChange) {
              //如果需要,则在元素挂载之后,给window添加hashchange事件
              window.addEventListener('hashchange', this.close);
            }
          });
        },
    
        beforeDestroy() {
          // 组件销毁时移除hashchange事件
          if (this.closeOnHashChange) {
            window.removeEventListener('hashchange', this.close);
          }
          setTimeout(() => {
            messageBox.closeDialog();
          });
        },
    
        data() {
          return {
            uid: 1,
            title: undefined, //MessageBox 标题
            message: '',  //MessageBox 消息正文内容
            type: '', //消息类型,用于显示图标
            iconClass: '', //自定义图标的类名,会覆盖 type
            customClass: '',
            showInput: false,  //是否显示输入框
            inputValue: null, //输入框的初始文本
            inputPlaceholder: '', //输入框的占位符
            inputType: 'text', //输入框的类型
            inputPattern: null,//输入框的校验表达式
            inputValidator: null, //输入框的校验函数。可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage
            inputErrorMessage: '', //校验未通过时的提示文本
            showConfirmButton: true, //是否显示确定按钮
            showCancelButton: false, //是否显示取消按钮
            action: '',
            confirmButtonText: '', //确定按钮的文本内容
            cancelButtonText: '', //取消按钮的文本内容
            confirmButtonLoading: false,
            cancelButtonLoading: false,
            confirmButtonClass: '',  //确定按钮的自定义类名
            confirmButtonDisabled: false,
            cancelButtonClass: '', //取消按钮的自定义类名
            editorErrorMessage: null,
            callback: null,
            dangerouslyUseHTMLString: false, //是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分
            focusAfterClosed: null,
            isOnComposition: false,
            distinguishCancelAndClose: false //是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分
          };
        }
      };
    </script>
    

    main.js

    const defaults = {
      title: null, //MessageBox 标题
      message: '', //MessageBox 消息正文内容
      type: '', //消息类型,用于显示图标
      iconClass: '', //自定义图标的类名,会覆盖 type
      showInput: false, //是否显示输入框
      showClose: true, //MessageBox 是否显示右上角关闭按钮
      modalFade: true,
      lockScroll: true,//是否在 MessageBox 出现时将 body 滚动锁定
      closeOnClickModal: true, //是否可通过点击遮罩关闭 MessageBox
      closeOnPressEscape: true, //是否可通过按下 ESC 键关闭 MessageBox
      closeOnHashChange: true, //是否在 hashchange 时关闭 MessageBox
      inputValue: null, //输入框的初始文本
      inputPlaceholder: '', //输入框的占位符
      inputType: 'text', //输入框的类型
      inputPattern: null, //输入框的校验表达式
      inputValidator: null, //输入框的校验函数。可以返回布尔值或字符串,若返回一个字符串, 则返回结果会被赋值给 inputErrorMessage
      inputErrorMessage: '', //校验未通过时的提示文本
      showConfirmButton: true, //是否显示确定按钮
      showCancelButton: false, //是否显示取消按钮
      confirmButtonPosition: 'right', //
      confirmButtonHighlight: false,
      cancelButtonHighlight: false,
      confirmButtonText: '', //确定按钮的文本内容
      cancelButtonText: '', //取消按钮的文本内容
      confirmButtonClass: '', //确定按钮的自定义类名
      cancelButtonClass: '', //取消按钮的自定义类名
      customClass: '', //MessageBox 的自定义类名
      beforeClose: null, //MessageBox 关闭前的回调,会暂停实例的关闭
      dangerouslyUseHTMLString: false, //是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分
      center: false, //是否居中布局
      roundButton: false, //是否使用圆角按钮
      distinguishCancelAndClose: false //是否将取消(点击取消按钮)与关闭(点击关闭按钮或遮罩层、按下 ESC 键)进行区分
    };
    
    import Vue from 'vue';
    import msgboxVue from './main.vue';
    import merge from 'element-ui/src/utils/merge';
    import { isVNode } from 'element-ui/src/utils/vdom';
    
    //创建MessageBox的构造器,包含msgboxVue组件选项的对象作为Vue.extend的参数,返回一个VueComponent类,VueComponent类是Vue类的子类
    //Vue.extend是一个类构造器,用来创建一个子类vue并返回构造函数,而Vue.component它的任务是将给定的构造函数与字符串ID相关联,以便Vue.js可以在模板中接收它。
    const MessageBoxConstructor = Vue.extend(msgboxVue);
    
    let currentMsg, instance;
    let msgQueue = [];
    
    const defaultCallback = action => {
      if (currentMsg) {
        let callback = currentMsg.callback;
        if (typeof callback === 'function') {
          if (instance.showInput) {
            callback(instance.inputValue, action);
          } else {
            callback(action);
          }
        }
        if (currentMsg.resolve) {
          // 点击确定或者去下关闭按钮时,在此处调对应的方法进行处理
          if (action === 'confirm') { //执行确认后的回调方法
            if (instance.showInput) {
              currentMsg.resolve({ value: instance.inputValue, action });
            } else {
              currentMsg.resolve(action);
            }
          } else if (currentMsg.reject && (action === 'cancel' || action === 'close')) {  //执行取消和关闭后的回调方法
            currentMsg.reject(action);
          }
        }
      }
    };
    
    const initInstance = () => {
      //instance为messageBox的实例
      instance = new MessageBoxConstructor({
        el: document.createElement('div')
      });
    
      instance.callback = defaultCallback;
    };
    
    const showNextMsg = () => {
      if (!instance) {
        // 调用initInstance初始化实例,返回messageBox的实例对象
        initInstance();
      }
      instance.action = '';
    
      if (!instance.visible || instance.closeTimer) {
        if (msgQueue.length > 0) {
          currentMsg = msgQueue.shift();
    
          let options = currentMsg.options;
          //将用户设置的属性和方法挂载到messageBox的实例对象instance上去
          for (let prop in options) {
            if (options.hasOwnProperty(prop)) {
              instance[prop] = options[prop];
            }
          }
          //当用户未设置callback时,将defaultCallback赋值给instance.callback
          if (options.callback === undefined) {
            instance.callback = defaultCallback;
          }
    
          let oldCb = instance.callback;
          instance.callback = (action, instance) => {
            oldCb(action, instance);
            showNextMsg();
          };
          if (isVNode(instance.message)) {
            instance.$slots.default = [instance.message];
            instance.message = null;
          } else {
            delete instance.$slots.default;
          }
          ['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape', 'closeOnHashChange'].forEach(prop => {
            if (instance[prop] === undefined) {
              instance[prop] = true;
            }
          });
          document.body.appendChild(instance.$el);
    
          Vue.nextTick(() => {
            instance.visible = true;
          });
        }
      }
    };
    
    const MessageBox = function(options, callback) {
      if (Vue.prototype.$isServer) return;
      if (typeof options === 'string' || isVNode(options)) {
        options = {
          message: options
        };
        if (typeof arguments[1] === 'string') {
          options.title = arguments[1];
        }
      } else if (options.callback && !callback) {
        callback = options.callback;
      }
    
      if (typeof Promise !== 'undefined') {
        return new Promise((resolve, reject) => { // eslint-disable-line
          // options合并默认的所有参数和用户设置的参数
          msgQueue.push({
            options: merge({}, defaults, MessageBox.defaults, options),
            callback: callback,
            resolve: resolve,
            reject: reject
          });
    
          showNextMsg();
        });
      } else {
        msgQueue.push({
          options: merge({}, defaults, MessageBox.defaults, options),
          callback: callback
        });
    
        showNextMsg();
      }
    };
    
    MessageBox.setDefaults = defaults => {
      MessageBox.defaults = defaults;
    };
    //this.$alert方法
    MessageBox.alert = (message, title, options) => {
      //如果title的类型为object时,用户可能没传title,则将title的值赋值给options
      if (typeof title === 'object') {
        options = title;
        title = '';
      } else if (title === undefined) {
        title = '';
      }
      //合并用户传的参数,并调用MessageBox方法
      return MessageBox(merge({
        title: title,
        message: message,
        $type: 'alert',
        closeOnPressEscape: false,
        closeOnClickModal: false
      }, options));
    };
    //this.$confirm方法,分析同MessageBox.alert方法
    MessageBox.confirm = (message, title, options) => {
      if (typeof title === 'object') {
        options = title;
        title = '';
      } else if (title === undefined) {
        title = '';
      }
    
      return MessageBox(merge({
        title: title,
        message: message,
        $type: 'confirm',
        showCancelButton: true
      }, options));
    };
    //this.$prompt方法,分析同MessageBox.alert方法
    MessageBox.prompt = (message, title, options) => {
      if (typeof title === 'object') {
        options = title;
        title = '';
      } else if (title === undefined) {
        title = '';
      }
      return MessageBox(merge({
        title: title,
        message: message,
        showCancelButton: true,
        showInput: true,
        $type: 'prompt'
      }, options));
    };
    
    MessageBox.close = () => {
      instance.doClose();
      instance.visible = false;
      msgQueue = [];
      currentMsg = null;
    };
    
    export default MessageBox;
    export { MessageBox };
    
    
  • 相关阅读:
    第5章 JDBC/ODBC服务器
    第4章 SparkSQL数据源
    第3章 SparkSQL解析
    第2章 执行SparkSQL查询
    第1章 Spark SQL概述
    Ubutun重启网卡
    Java面试通关要点汇总整理
    40道Java基础常见面试题及详细答案
    ListView
    数据库表及字段命名规范
  • 原文地址:https://www.cnblogs.com/fangnianqin/p/10769949.html
Copyright © 2020-2023  润新知