MessageBox組件源碼,有添加部分註釋javascript
<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>
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 };