如何使用Bootstrap的modal組件自訂alert,confirm和modal對話方塊,bootstrapmodal
本文我將為大家介紹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輸入admin@admin 的時候,會給出提交成功的提示,確定之後就會關閉所有的彈框:
當email輸入其它值得時候,會給出提交失敗的提示,並且modal框會依然顯示在那裡:
在組件定義裡面,尤其是註冊按鈕這一塊,我加了一些AOP編程的處理,同時利用了jquery的延遲物件,來實現我需要的非同步編程,詳情請閱讀源碼,有問題可以在評論區交流賜教。
2. 組件需求
有時候為了寫一個好用的組件,只需要把它的大概原型和要對外部提供的介面確定下來,就已經完成這個組件編寫最重要的工作了,雖然還沒有開始編碼。以本文要編寫的這幾個組件來說,我想要的這幾個組件的原型和調用形式分別是這樣的:
1)自訂alert框
原型是:
調用時最多需要兩個參數,一個msg用來傳遞要顯示的提示內容,一個onOk用來處理確定按鈕點擊時候的回調,調用形式有以下2種:
//1Alert('您選擇的訂單狀態不符合當前操作的條件,請重新整理列表顯示最新資料後再繼續操作!');//2Alert({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 === 'admin@admin') {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') {//非同步任務只有在成功回調的時候關閉Modalret.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('');};//延遲初始化modalif(!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組件過程中的一些方法和實踐,代碼偏多,不容易引起人的閱讀興趣,但是文中介紹的方法比較簡單,而且這三個組件我已經用到好幾個項目裡面,從目前來看,能夠解決我所有需要的彈框需求,所以我把它推薦出來,希望能給有需要的人帶來協助。
您可能感興趣的文章:
- JS中confirm,alert,prompt函數使用區別分析
- jQuery)擴充jQuery系列之一 類比alert,confirm(一)
- JS中confirm,alert,prompt函數區別分析
- javascript學習筆記(十六) 系統對話方塊(alert、confirm、prompt)
- 自編jQuery外掛程式實現類比alert和confirm