如何使用Bootstrap的modal元件自定義alert,confirm和modal對話方塊

如何使用Bootstrap的modal元件自定義alert,confirm和modal對話方塊

本文我將為大家介紹Bootstrap中的彈出視窗元件Modal,此元件簡單易用,效果大氣漂亮且很實用!

由於瀏覽器提供的alert和confirm框體驗不好,而且瀏覽器沒有提供一個標準的以對話方塊的形式顯示自定義HTML的彈框函式,所以很多專案都會自定義對話方塊元件。本篇文章介紹自己在專案中基於bootstrap的modal元件,自定義alert,confirm和modal對話方塊的經驗,相對比較簡單實用,希望能對你有所參考價值。

1. 例項展示

詳細的程式碼可通過前面給出的下載連結下載原始碼去了解,程式碼量不大,這三個元件加起來只有200多行

如果你有javascript的元件開發經驗,我這個層級的程式碼相信你一下子就能看明白。原始碼中我還給出了一個demo,這個demo模擬了一個比較貼近現實需求的一個場景:

1)使用者點選介面上的某個按鈕,開啟之前定義的一個modal框:

2)使用者在開啟的modal框內填寫一些表單,點選確定的時候,會觸發一些校驗:

沒填email時:

填寫了email之後:

這兩個提示其實是為了演示Alert和Confirm的效果硬塞進去的,實際上可能沒有這麼彆扭的功能。

3)在提示Password為空的時候,細心的人會發現那個確定按鈕處於一個禁用的狀態,這個考慮是因為確定按鈕最終要完成的是一些非同步任務,在非同步任務成功完成之前,我希望modal元件都不要關閉,並且能夠控制已點選的按鈕不能重複點選;

4)我用setTimeout模擬了一個非同步任務,這個非同步任務在點選確定按鈕之後,3s才會回撥,並且:

當email輸入[email protected] 的時候,會給出提交成功的提示,確定之後就會關閉所有的彈框:

當email輸入其它值得時候,會給出提交失敗的提示,並且modal框會依然顯示在那裡:

在元件定義裡面,尤其是註冊按鈕這一塊,我加了一些AOP程式設計的處理,同時利用了jquery的延遲物件,來實現我需要的非同步程式設計,詳情請閱讀原始碼,有問題可以在評論區交流賜教。

2. 元件需求

有時候為了寫一個好用的元件,只需要把它的大概原型和要對外部提供的介面確定下來,就已經完成這個元件編寫最重要的工作了,雖然還沒有開始編碼。以本文要編寫的這幾個元件來說,我想要的這幾個元件的原型和呼叫形式分別是這樣的:

1)自定義alert框

原型是:

呼叫時最多需要兩個引數,一個msg用來傳遞要顯示的提示內容,一個onOk用來處理確定按鈕點選時候的回撥,呼叫形式有以下2種:


//1
Alert('您選擇的訂單狀態不符合當前操作的條件,請重新整理列表顯示最新資料後再繼續操作!');
//2
Alert({
msg: '您選擇的訂單狀態不符合當前操作的條件,請重新整理列表顯示最新資料後再繼續操作!',
onOk: function(){
}
}); 

第一種是沒有回撥的情況,那麼直接傳遞msg即可,第二種是有回撥的情況,用options物件的方式來傳遞msg和onOks回撥這兩個引數。不管onOk回撥有沒有,點選按鈕的時候都要關閉彈框。

2)自定義confirm框

這個框的原型跟alert框只差一個按鈕:

呼叫形式只有一種:


Confirm({
msg: '您選擇的訂單狀態不符合當前操作的條件,請確認是否要繼續操作!',
onOk: function(){
},
onCancel: function(){
}
}); 

onCancel是在點選取消按鈕時候的回撥。不管onOk和onCancel回撥有沒有,點選按鈕的時候都要關閉彈框。onCancel回撥可以沒有。

3)自定義modal框

原型:

呼叫形式:


var modal = new Modal({
title: '',
content: '',
width: 600,
buttons: [
{
html: '<button type="button" class="btn btn-sm btn-primary btn-ok">確定</button>',
selector: '.btn-ok',
callback: function(){
//點選確定按鈕的回撥
}
},
{
html: '<button type="button" class="btn btn-sm btn-default btn-cancel">取消</button>',
selector: '.btn-cancel',
callback: function(){
//點選取消按鈕的回撥
}
}
],
onContentReady: function(){
//當modal新增到DOM並且初始化完畢時的事件回撥,每個modal例項這個回撥只會被觸發一次
},
onContentChange: function(){
//當呼叫modal.setContent類似的方法改變了modal內容時的事件回撥
},
onModalShow: function(){
//當呼叫modal.open類似方法顯示modal時都會觸發的事件回撥
},
onModalHide: function(){
//當呼叫modal.hide類似方法隱藏modal時都會觸發的事件回撥
}
});
$('#btn-audit').click(function(){
modal.open();
}); 

跟Alert和Confirm不同的是,一個頁面裡面只需要一個Alert和Confirm的例項,但是可能需要多個Modal的例項,所以每個Modal物件都需要單獨new一下。由於每個Modal要完成的事情都不相同,所以:

需要一個title引數來設定名稱,表達這個Modal正在處理的事情;

content參數列示Modal的html內容;

width引數設定Modal的寬度,Modal的高度保持auto;

buttons引數用來配置這個Modal上面的按鈕,一般情況下Modal元件只需要兩個按鈕(確定和取消)就夠了,但也有少數情況需要多個按鈕,所以把按鈕做成配置的方式相對靈活一點,每個按鈕用三個引數來配置,html表示按鈕的html結構,selector方便註冊回撥的時候通過事件委託的方式來處理,callback配置按鈕點選時的回撥;

onContentReady這個事件回撥,可以在Modal初始化完畢的時候,主動去初始化Modal內部html的一些元件;由於元件初始化一般只進行一次,所以放在這個回撥裡面最合適;

onContentChange回撥,在一個Modal需要被用作不同的場景,顯示不同的HTML的內容時會派上用場,但是不是非常的好用,處理起來邏輯會稍微偏複雜,如果一個Modal例項只做一件事情的時候,onContentChange這個回撥就用不到了;

onModalShow這個回撥在每次顯示Modal的時候都會顯示,使用的場景有很多,比如某個Modal用來填寫一些表單內容,下次填寫的時候需要reset一下表單才能給使用者使用,這種處理在這個回撥裡面處理就比較合適;

onModalHide這個回撥有用,不過能夠用到的場景不多,算是預留的一個介面。

4)其它需求

所有型別的彈框都做成虛擬模態的形式,顯示框的同時加一個遮罩;

所有框都不需要支援拖動和大小調整;

alert和dialog框的標題,按鈕數量、按鈕位置、按鈕文字都固定。

實際上:

遮罩這個效果,bootstrap的modal元件本身就已經支援了;

拖動和大小調整,這個功能屬於錦上添花,但是對軟體本身來說,並一定有多少額外的好處,所以我選擇不做這種多餘的處理;

alert和dialog不需要做太過個性化,能夠統一風格,改變瀏覽器原生的彈框體驗即可。

5)DEMO中呼叫例項

接下來演示下我在完成這三個元件開發之後,實際使用過程中呼叫這些元件的方式:


var modal = new Modal({
title: '測試modal',
content: $('#modal-tpl').html(),
width: 500,
onOk: function(){
var $form = this.$modal.find('form');
var data = $form.serializeArray();
var postData = {};
data.forEach(function(obj){
postData[obj.name] = obj.value;
});
if(!postData.email) {
Alert('請輸入EMAIL!');
return false;
}
var deferred = $.Deferred();
if(!postData.password) {
Confirm({
msg: 'Password為空,是否要繼續?',
onOk: function(){
_post();
},
onCancel: function(){
deferred.reject();
}
})
} else {
_post();
}
return $.when(deferred);
function _post(){
//模擬非同步任務
setTimeout(function(){
if(postData.email === '[email protected]') {
Alert({
msg: '提交成功!',
onOk: function(){
deferred.resolve();
}
});
} else {
Alert({
msg: '提交失敗!',
onOk: function(){
deferred.reject();
}
});
}
},3000);
}
},
onModalShow: function () {
var $form = this.$modal.find('form');
$form[0].reset();
}
});
$('#btn-modal').click(function () {
modal.open();
}); 

3. 實現要點

1)最基礎的一點,要對bootstrap的modal元件原始碼有所瞭解:

初始化方式:$modal.modal()

開啟:$modal.modal(‘show’)

關閉:$modal.modal(hide)

事件:bootstrap大部分帶過渡效果的元件的事件都是成對的,並且一個是現在時,一個是完成時,modal元件定義了2對:

show.bs.modal和shown.bs.modal,hide.bs.modal和hidden.bs.modal。

這兩對事件分別在開啟和關閉的過渡效果執行前後觸發。從我要定義的元件需求來說,定義元件的時候需要show.bs.modal和hidden.bs.modal這兩個事件,在偵聽到bootstrap的modal元件派發這兩個事件的時候,派發自己定義的元件的事件:

modalShow和modalHide。

選項:

backdrop: 是否顯示遮罩;
keyboard: 是否支援鍵盤迴調;
show:是否在初始化完畢就立即顯示。

這三個選項預設都是true,但是在我定義元件的時候,我都配置成了false,鍵盤迴調這種特性暫時不考慮,所以配置為true;當

呼叫bootstrap的modal初始化的時候當然不能立即顯示彈框,所以也不能配置為true;backdrop配置為false的原因在下一點介紹。

2)遮罩處理

如果啟用bootstrap的遮罩,會發現在點選遮罩部分的時候,彈框就會自動關掉了,這不是我期望的虛擬模態效果,所以必須把backdrop配置為false。但是把這個選項配置為false之後,又會引發一個新問題,就是元件沒有了遮罩效果,所以為了兼顧這兩個問題,只能自己寫一個簡單的遮罩處理:


var $body = $(document.body),
BackDrop = (function () {
var $backDrop,
count = 0,
create = function () {
$backDrop = $('<div class="modal-backdrop fade in"></div>').appendTo($body);
};
return {
show: function () {
!$backDrop && create();
$backDrop[0].style.display = 'block';
count  ;
},
hide: function () {
count--;
if (!count) {
$backDrop.remove();
$backDrop = undefined;
}
}
}
})(); 

這段程式碼中引入count變數的原因是因為BackDrop是一個全域性的單例物件,當呼叫多個modal例項的open方法的時候,都會呼叫BackDrop的show方法,為了保證在呼叫BackDrop的hide方法時,能夠確保在所有的modal例項都關閉之後再隱藏Backdrop,所以就加了一個count變數來記錄BackDrop的show方法被呼叫了多少次,只有當count為0的時候,呼叫BackDrop的hide方法才會真正隱藏BackDrop。

3)元件的選項的預設值定義


ModalDialog.defaults = {
title: '',
content: '',
width: 600,
buttons: [
{
html: '<button type="button" class="btn btn-sm btn-primary btn-ok">確定</button>',
selector: '.btn-ok',
callback: getDefaultBtnCallbackProxy('onOk')
},
{
html: '<button type="button" class="btn btn-sm btn-default btn-cancel">取消</button>',
selector: '.btn-cancel',
callback: getDefaultBtnCallbackProxy('onCancel')
}
],
onOk: $.noop,
onCancel: $.noop,
onContentReady: $.noop,
onContentChange: $.noop,//content替換之後的回撥
onModalShow: $.noop,
onModalHide: $.noop//modal關閉之後的回撥
}; 

通過buttons配置兩個預設的按鈕,確定和取消,然後為了簡化這兩個預設按鈕的回撥配置,把這兩個按鈕的介面進一步擴充套件到了上一級別,onOk和onCancel分別會在點選確定和取消的時候被呼叫,這兩個選項完全是函式回撥,不像onContentReady這種屬於事件回撥。getDefaultBtnCallbackProxy用來輔助註冊onOk和onCancel:


var getDefaultBtnCallbackProxy = function (callbackName) {
return function () {
var opts = this.options,
callback = opts[callbackName] && typeof opts[callbackName] === 'function' ? opts[callbackName] : '';
return callback && callback.apply(this, arguments);
}
}

裡面的this會被繫結到modal例項上。

4)建構函式:


function ModalDialog(options) {
this.options = this.getOptions(options);
this.$modal = undefined;
this.$modalTitle = undefined;
this.$modalBody = undefined;
this.$modalFooter = undefined;
this.state = undefined;
}

這個主要是宣告瞭用到的一些例項變數。

5)關鍵的原型方法和函式


open: function (state) {
this.state = state;
!this.$modal && initModal(this, this.options);
BackDrop.show();
this.$modal.modal('show');
}

這是個原型方法,元件的初始化也是在這個方法呼叫的時候執行的(延遲初始化)。


initModal = function (that, opts) {
var $modal = createModal(that);
that.setTitle(opts.title);
that.setContent(opts.content);
that.addButtons(opts.buttons);
that.setWidth(opts.width);
bindHandler(that, opts);
$modal.modal();//呼叫bootstrap的Modal元件
$modal.trigger('contentReady');
}

這是個函式,用來初始化元件。其中:

setTitle是個原型方法,用來設定modal的標題;

setContent是個原型方法,用來設定modal的html內容;

addButtons是個原型方法,用來註冊按鈕;

setWidth是個原型方法,用來設定modal的寬度;

bindHandler是個函式,用來註冊modal的那些事件;

倒數第二步呼叫$modal.modal()初始化bootstrap的modal元件;

最後一步觸發contentReady事件。

bindHandler原始碼:


bindHandler = function (that, opts) {
var $modal = that.$modal;
typeof opts.onContentChange === 'function' && $modal.on('contentChange', $.proxy(opts.onContentChange, that));
typeof opts.onContentReady === 'function' && $modal.on('contentReady', $.proxy(opts.onContentReady, that));
typeof opts.onModalShow === 'function' && $modal.on('modalShow', $.proxy(opts.onModalShow, that));
typeof opts.onModalHide === 'function' && $modal.on('modalHide', $.proxy(opts.onModalHide, that));
$modal.on('show.bs.modal', function () {
$modal.trigger('modalShow');
});
$modal.on('hidden.bs.modal', function () {
$modal.trigger('modalHide');
});
}

為了方便使用,我把onContentChange這幾個回撥的上下文繫結到了當前的modal例項。最後兩個事件偵聽就是把bootstrap的事件封裝成了我定義的modal事件。

addButtons原始碼:


addButtons: function (buttons) {
var buttons = !$.isArray(buttons) ? [] : buttons,
that = this,
htmlS = [];
buttons.forEach(function (btn) {
htmlS.push(btn.html);
btn.selector && that.$modal.on('click', btn.selector, $.proxy(function (e) {
var self = this,
$btn = $(e.currentTarget);
//先禁用按鈕
$btn[0].disabled = true;
var callback = typeof btn.callback === 'function' ? btn.callback : '',
ret = callback && callback.apply(self, arguments);
if (ret === false) {
$btn[0].disabled = false;
return;
}
if (typeof(ret) === 'object' && 'done' in ret && typeof ret['done'] === 'function') {
//非同步任務只有在成功回撥的時候關閉Modal
ret.done(function () {
that.hide();
}).always(function () {
$btn[0].disabled = false;
});
} else {
$btn[0].disabled = false;
that.hide();
}
}, that));
});
this.$modalFooter.prepend($(htmlS.join('')));
}

從這個程式碼可以看出:

當按鈕點選之後,按鈕就會被禁用;

當按鈕返回false的時候,按鈕恢復,但是modal不會被關閉,說明當前的一些操作被程式碼給攔下來了;

當按鈕返回的是一個延遲物件的時候,會等到延遲物件完成的時候才會恢復按鈕,並且只有在延遲物件resolve的時候才會關閉modal;

否則就恢復按鈕,並主動關閉modal。

在這段程式碼裡面考慮了:

按鈕的防重複點選,modal的自動關閉以及非同步任務的處理。

6)封裝Alert和Confirm

Alert和Confirm其實就是一個特殊的modal,另外這兩個元件還可以共用一個modal,瞭解到這些基礎之後,元件就可以這樣定義:


var Alert, Confirm;
(function () {
var modal,
Proxy = function (isAlert) {
return function () {
if (arguments.length != 1) return;
var msg = typeof arguments[0] === 'string' && arguments[0] || arguments[0].msg || '',
onOk = typeof arguments[0] === 'object' && typeof arguments[0].onOk === 'function' && arguments[0].onOk,
onCancel = typeof arguments[0] === 'object' && typeof arguments[0].onCancel === 'function' && arguments[0].onCancel,
width = typeof arguments[0] === 'object' && arguments[0].width || 400,
_onModalShow = function () {
this.setWidth(width);
this.setContent(msg);
this[(isAlert ? 'hide' : 'show')   'Button']('.btn-cancel');
},
_onModalHide = function () {
this.setContent('');
};
//延遲初始化modal
if(!modal) {
modal = new Modal({
'title': '操作提示',
onModalShow: _onModalShow,
onModalHide: _onModalHide,
onContentReady: function(){
this.$modalBody.css({
'padding-top': '30px',
'padding-bottom': '30px'
})
}
});
} else {
//如果modal已經初始化則需要重新監聽事件
var $modal = modal.$modal;
$modal.off('modalShow modalHide');
$modal.off('modalShow modalHide');
$modal.on('modalShow', $.proxy(_onModalShow, modal));
$modal.on('modalHide', $.proxy(_onModalHide, modal));
}
modal.setOptions({
onOk: onOk || $.noop,
onCancel: onCancel || $.noop
});
modal.open();
}
};
Alert = Proxy(true);
Confirm = Proxy();
})(); 

這段程式碼裡:

首先考慮到了延遲初始化這個全域性的modal元件;

由於onModalHide和onModalShow這兩個回撥屬於事件回撥,在初始化元件的時候通過options傳進去的引數,不能通過修改options的方式來更改回撥,只能通過重新註冊的方式來處理;而onOk和onCancel屬於函式回撥,只要更改了options裡面的引用,回撥就能更改;

考慮到Alert和Confirm內容的長短,新加了一個引數width,以便調節框的寬度。

4. 小結

本文介紹的是自己在定義js元件過程中的一些方法和實踐,程式碼偏多,不容易引起人的閱讀興趣,但是文中介紹的方法比較簡單,而且這三個元件我已經用到好幾個專案裡面,從目前來看,能夠解決我所有需要的彈框需求,所以我把它推薦出來,希望能給有需要的人帶來幫助。

您可能感興趣的文章:

bootstrap confirmation按鈕提示元件使用詳解集合Bootstrap自定義confirm提示效果