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 };