如何使用Bootstrap的modal組件自定義alert,confirm和modal對話框
本文我將為大家介紹Bootstrap中的彈出窗口組件Modal,此組件簡單易用,效果大氣漂亮且很實(shí)用!
由于瀏覽器提供的alert和confirm框體驗(yàn)不好,而且瀏覽器沒有提供一個(gè)標(biāo)準(zhǔn)的以對話框的形式顯示自定義HTML的彈框函數(shù),所以很多項(xiàng)目都會(huì)自定義對話框組件。本篇文章介紹自己在項(xiàng)目中基于bootstrap的modal組件,自定義alert,confirm和modal對話框的經(jīng)驗(yàn),相對比較簡單實(shí)用,希望能對你有所參考價(jià)值。
1. 實(shí)例展示
詳細(xì)的代碼可通過前面給出的下載鏈接下載源碼去了解,代碼量不大,這三個(gè)組件加起來只有200多行
如果你有javascript的組件開發(fā)經(jīng)驗(yàn),我這個(gè)層級的代碼相信你一下子就能看明白。源碼中我還給出了一個(gè)demo,這個(gè)demo模擬了一個(gè)比較貼近現(xiàn)實(shí)需求的一個(gè)場景:
1)用戶點(diǎn)擊界面上的某個(gè)按鈕,打開之前定義的一個(gè)modal框:
2)用戶在打開的modal框內(nèi)填寫一些表單,點(diǎn)擊確定的時(shí)候,會(huì)觸發(fā)一些校驗(yàn):
沒填email時(shí):
填寫了email之后:
這兩個(gè)提示其實(shí)是為了演示Alert和Confirm的效果硬塞進(jìn)去的,實(shí)際上可能沒有這么別扭的功能。
3)在提示Password為空的時(shí)候,細(xì)心的人會(huì)發(fā)現(xiàn)那個(gè)確定按鈕處于一個(gè)禁用的狀態(tài),這個(gè)考慮是因?yàn)榇_定按鈕最終要完成的是一些異步任務(wù),在異步任務(wù)成功完成之前,我希望modal組件都不要關(guān)閉,并且能夠控制已點(diǎn)擊的按鈕不能重復(fù)點(diǎn)擊;
4)我用setTimeout模擬了一個(gè)異步任務(wù),這個(gè)異步任務(wù)在點(diǎn)擊確定按鈕之后,3s才會(huì)回調(diào),并且:
當(dāng)email輸入admin@admin 的時(shí)候,會(huì)給出提交成功的提示,確定之后就會(huì)關(guān)閉所有的彈框:
當(dāng)email輸入其它值得時(shí)候,會(huì)給出提交失敗的提示,并且modal框會(huì)依然顯示在那里:
在組件定義里面,尤其是注冊按鈕這一塊,我加了一些AOP編程的處理,同時(shí)利用了jquery的延遲對象,來實(shí)現(xiàn)我需要的異步編程,詳情請閱讀源碼,有問題可以在評論區(qū)交流賜教。
2. 組件需求
有時(shí)候?yàn)榱藢懸粋€(gè)好用的組件,只需要把它的大概原型和要對外部提供的接口確定下來,就已經(jīng)完成這個(gè)組件編寫最重要的工作了,雖然還沒有開始編碼。以本文要編寫的這幾個(gè)組件來說,我想要的這幾個(gè)組件的原型和調(diào)用形式分別是這樣的:
1)自定義alert框
原型是:
調(diào)用時(shí)最多需要兩個(gè)參數(shù),一個(gè)msg用來傳遞要顯示的提示內(nèi)容,一個(gè)onOk用來處理確定按鈕點(diǎn)擊時(shí)候的回調(diào),調(diào)用形式有以下2種:
//1 Alert('您選擇的訂單狀態(tài)不符合當(dāng)前操作的條件,請刷新列表顯示最新數(shù)據(jù)后再繼續(xù)操作!'); //2 Alert({ msg: '您選擇的訂單狀態(tài)不符合當(dāng)前操作的條件,請刷新列表顯示最新數(shù)據(jù)后再繼續(xù)操作!', onOk: function(){ } });
第一種是沒有回調(diào)的情況,那么直接傳遞msg即可,第二種是有回調(diào)的情況,用options對象的方式來傳遞msg和onOks回調(diào)這兩個(gè)參數(shù)。不管onOk回調(diào)有沒有,點(diǎn)擊按鈕的時(shí)候都要關(guān)閉彈框。
2)自定義confirm框
這個(gè)框的原型跟alert框只差一個(gè)按鈕:
調(diào)用形式只有一種:
Confirm({ msg: '您選擇的訂單狀態(tài)不符合當(dāng)前操作的條件,請確認(rèn)是否要繼續(xù)操作!', onOk: function(){ }, onCancel: function(){ } });
onCancel是在點(diǎn)擊取消按鈕時(shí)候的回調(diào)。不管onOk和onCancel回調(diào)有沒有,點(diǎn)擊按鈕的時(shí)候都要關(guān)閉彈框。onCancel回調(diào)可以沒有。
3)自定義modal框
原型:
調(diào)用形式:
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(){ //點(diǎn)擊確定按鈕的回調(diào) } }, { html: '<button type="button" class="btn btn-sm btn-default btn-cancel">取消</button>', selector: '.btn-cancel', callback: function(){ //點(diǎn)擊取消按鈕的回調(diào) } } ], onContentReady: function(){ //當(dāng)modal添加到DOM并且初始化完畢時(shí)的事件回調(diào),每個(gè)modal實(shí)例這個(gè)回調(diào)只會(huì)被觸發(fā)一次 }, onContentChange: function(){ //當(dāng)調(diào)用modal.setContent類似的方法改變了modal內(nèi)容時(shí)的事件回調(diào) }, onModalShow: function(){ //當(dāng)調(diào)用modal.open類似方法顯示modal時(shí)都會(huì)觸發(fā)的事件回調(diào) }, onModalHide: function(){ //當(dāng)調(diào)用modal.hide類似方法隱藏modal時(shí)都會(huì)觸發(fā)的事件回調(diào) } }); $('#btn-audit').click(function(){ modal.open(); });
跟Alert和Confirm不同的是,一個(gè)頁面里面只需要一個(gè)Alert和Confirm的實(shí)例,但是可能需要多個(gè)Modal的實(shí)例,所以每個(gè)Modal對象都需要單獨(dú)new一下。由于每個(gè)Modal要完成的事情都不相同,所以:
需要一個(gè)title參數(shù)來設(shè)置名稱,表達(dá)這個(gè)Modal正在處理的事情;
content參數(shù)表示Modal的html內(nèi)容;
width參數(shù)設(shè)置Modal的寬度,Modal的高度保持auto;
buttons參數(shù)用來配置這個(gè)Modal上面的按鈕,一般情況下Modal組件只需要兩個(gè)按鈕(確定和取消)就夠了,但也有少數(shù)情況需要多個(gè)按鈕,所以把按鈕做成配置的方式相對靈活一點(diǎn),每個(gè)按鈕用三個(gè)參數(shù)來配置,html表示按鈕的html結(jié)構(gòu),selector方便注冊回調(diào)的時(shí)候通過事件委托的方式來處理,callback配置按鈕點(diǎn)擊時(shí)的回調(diào);
onContentReady這個(gè)事件回調(diào),可以在Modal初始化完畢的時(shí)候,主動(dòng)去初始化Modal內(nèi)部html的一些組件;由于組件初始化一般只進(jìn)行一次,所以放在這個(gè)回調(diào)里面最合適;
onContentChange回調(diào),在一個(gè)Modal需要被用作不同的場景,顯示不同的HTML的內(nèi)容時(shí)會(huì)派上用場,但是不是非常的好用,處理起來邏輯會(huì)稍微偏復(fù)雜,如果一個(gè)Modal實(shí)例只做一件事情的時(shí)候,onContentChange這個(gè)回調(diào)就用不到了;
onModalShow這個(gè)回調(diào)在每次顯示Modal的時(shí)候都會(huì)顯示,使用的場景有很多,比如某個(gè)Modal用來填寫一些表單內(nèi)容,下次填寫的時(shí)候需要reset一下表單才能給用戶使用,這種處理在這個(gè)回調(diào)里面處理就比較合適;
onModalHide這個(gè)回調(diào)有用,不過能夠用到的場景不多,算是預(yù)留的一個(gè)接口。
4)其它需求
所有類型的彈框都做成虛擬模態(tài)的形式,顯示框的同時(shí)加一個(gè)遮罩;
所有框都不需要支持拖動(dòng)和大小調(diào)整;
alert和dialog框的標(biāo)題,按鈕數(shù)量、按鈕位置、按鈕文字都固定。
實(shí)際上:
遮罩這個(gè)效果,bootstrap的modal組件本身就已經(jīng)支持了;
拖動(dòng)和大小調(diào)整,這個(gè)功能屬于錦上添花,但是對軟件本身來說,并一定有多少額外的好處,所以我選擇不做這種多余的處理;
alert和dialog不需要做太過個(gè)性化,能夠統(tǒng)一風(fēng)格,改變?yōu)g覽器原生的彈框體驗(yàn)即可。
5)DEMO中調(diào)用實(shí)例
接下來演示下我在完成這三個(gè)組件開發(fā)之后,實(shí)際使用過程中調(diào)用這些組件的方式:
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為空,是否要繼續(xù)?', onOk: function(){ _post(); }, onCancel: function(){ deferred.reject(); } }) } else { _post(); } return $.when(deferred); function _post(){ //模擬異步任務(wù) 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. 實(shí)現(xiàn)要點(diǎn)
1)最基礎(chǔ)的一點(diǎn),要對bootstrap的modal組件源碼有所了解:
初始化方式:$modal.modal()
打開:$modal.modal('show')
關(guān)閉:$modal.modal(hide)
事件:bootstrap大部分帶過渡效果的組件的事件都是成對的,并且一個(gè)是現(xiàn)在時(shí),一個(gè)是完成時(shí),modal組件定義了2對:
show.bs.modal和shown.bs.modal,hide.bs.modal和hidden.bs.modal。
這兩對事件分別在打開和關(guān)閉的過渡效果執(zhí)行前后觸發(fā)。從我要定義的組件需求來說,定義組件的時(shí)候需要show.bs.modal和hidden.bs.modal這兩個(gè)事件,在偵聽到bootstrap的modal組件派發(fā)這兩個(gè)事件的時(shí)候,派發(fā)自己定義的組件的事件:
modalShow和modalHide。
選項(xiàng):
backdrop: 是否顯示遮罩;
keyboard: 是否支持鍵盤回調(diào);
show:是否在初始化完畢就立即顯示。
這三個(gè)選項(xiàng)默認(rèn)都是true,但是在我定義組件的時(shí)候,我都配置成了false,鍵盤回調(diào)這種特性暫時(shí)不考慮,所以配置為true;當(dāng)
調(diào)用bootstrap的modal初始化的時(shí)候當(dāng)然不能立即顯示彈框,所以也不能配置為true;backdrop配置為false的原因在下一點(diǎn)介紹。
2)遮罩處理
如果啟用bootstrap的遮罩,會(huì)發(fā)現(xiàn)在點(diǎn)擊遮罩部分的時(shí)候,彈框就會(huì)自動(dòng)關(guān)掉了,這不是我期望的虛擬模態(tài)效果,所以必須把backdrop配置為false。但是把這個(gè)選項(xiàng)配置為false之后,又會(huì)引發(fā)一個(gè)新問題,就是組件沒有了遮罩效果,所以為了兼顧這兩個(gè)問題,只能自己寫一個(gè)簡單的遮罩處理:
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變量的原因是因?yàn)锽ackDrop是一個(gè)全局的單例對象,當(dāng)調(diào)用多個(gè)modal實(shí)例的open方法的時(shí)候,都會(huì)調(diào)用BackDrop的show方法,為了保證在調(diào)用BackDrop的hide方法時(shí),能夠確保在所有的modal實(shí)例都關(guān)閉之后再隱藏Backdrop,所以就加了一個(gè)count變量來記錄BackDrop的show方法被調(diào)用了多少次,只有當(dāng)count為0的時(shí)候,調(diào)用BackDrop的hide方法才會(huì)真正隱藏BackDrop。
3)組件的選項(xiàng)的默認(rèn)值定義
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替換之后的回調(diào) onModalShow: $.noop, onModalHide: $.noop//modal關(guān)閉之后的回調(diào) };
通過buttons配置兩個(gè)默認(rèn)的按鈕,確定和取消,然后為了簡化這兩個(gè)默認(rèn)按鈕的回調(diào)配置,把這兩個(gè)按鈕的接口進(jìn)一步擴(kuò)展到了上一級別,onOk和onCancel分別會(huì)在點(diǎn)擊確定和取消的時(shí)候被調(diào)用,這兩個(gè)選項(xiàng)完全是函數(shù)回調(diào),不像onContentReady這種屬于事件回調(diào)。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會(huì)被綁定到modal實(shí)例上。
4)構(gòu)造函數(shù):
function ModalDialog(options) { this.options = this.getOptions(options); this.$modal = undefined; this.$modalTitle = undefined; this.$modalBody = undefined; this.$modalFooter = undefined; this.state = undefined; }
這個(gè)主要是聲明了用到的一些實(shí)例變量。
5)關(guān)鍵的原型方法和函數(shù)
open: function (state) { this.state = state; !this.$modal && initModal(this, this.options); BackDrop.show(); this.$modal.modal('show'); }
這是個(gè)原型方法,組件的初始化也是在這個(gè)方法調(diào)用的時(shí)候執(zhí)行的(延遲初始化)。
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();//調(diào)用bootstrap的Modal組件 $modal.trigger('contentReady'); }
這是個(gè)函數(shù),用來初始化組件。其中:
setTitle是個(gè)原型方法,用來設(shè)置modal的標(biāo)題;
setContent是個(gè)原型方法,用來設(shè)置modal的html內(nèi)容;
addButtons是個(gè)原型方法,用來注冊按鈕;
setWidth是個(gè)原型方法,用來設(shè)置modal的寬度;
bindHandler是個(gè)函數(shù),用來注冊modal的那些事件;
倒數(shù)第二步調(diào)用$modal.modal()初始化bootstrap的modal組件;
最后一步觸發(fā)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這幾個(gè)回調(diào)的上下文綁定到了當(dāng)前的modal實(shí)例。最后兩個(gè)事件偵聽就是把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') { //異步任務(wù)只有在成功回調(diào)的時(shí)候關(guān)閉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(''))); }
從這個(gè)代碼可以看出:
當(dāng)按鈕點(diǎn)擊之后,按鈕就會(huì)被禁用;
當(dāng)按鈕返回false的時(shí)候,按鈕恢復(fù),但是modal不會(huì)被關(guān)閉,說明當(dāng)前的一些操作被代碼給攔下來了;
當(dāng)按鈕返回的是一個(gè)延遲對象的時(shí)候,會(huì)等到延遲對象完成的時(shí)候才會(huì)恢復(fù)按鈕,并且只有在延遲對象resolve的時(shí)候才會(huì)關(guān)閉modal;
否則就恢復(fù)按鈕,并主動(dòng)關(guān)閉modal。
在這段代碼里面考慮了:
按鈕的防重復(fù)點(diǎn)擊,modal的自動(dòng)關(guān)閉以及異步任務(wù)的處理。
6)封裝Alert和Confirm
Alert和Confirm其實(shí)就是一個(gè)特殊的modal,另外這兩個(gè)組件還可以共用一個(gè)modal,了解到這些基礎(chǔ)之后,組件就可以這樣定義:
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已經(jīng)初始化則需要重新監(jiān)聽事件 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(); })();
這段代碼里:
首先考慮到了延遲初始化這個(gè)全局的modal組件;
由于onModalHide和onModalShow這兩個(gè)回調(diào)屬于事件回調(diào),在初始化組件的時(shí)候通過options傳進(jìn)去的參數(shù),不能通過修改options的方式來更改回調(diào),只能通過重新注冊的方式來處理;而onOk和onCancel屬于函數(shù)回調(diào),只要更改了options里面的引用,回調(diào)就能更改;
考慮到Alert和Confirm內(nèi)容的長短,新加了一個(gè)參數(shù)width,以便調(diào)節(jié)框的寬度。
4. 小結(jié)
本文介紹的是自己在定義js組件過程中的一些方法和實(shí)踐,代碼偏多,不容易引起人的閱讀興趣,但是文中介紹的方法比較簡單,而且這三個(gè)組件我已經(jīng)用到好幾個(gè)項(xiàng)目里面,從目前來看,能夠解決我所有需要的彈框需求,所以我把它推薦出來,希望能給有需要的人帶來幫助。
相關(guān)文章
js實(shí)現(xiàn)鼠標(biāo)移入移出卡片切換內(nèi)容
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)鼠標(biāo)移入移出卡片切換內(nèi)容,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10微信小程序車牌號碼模擬鍵盤輸入功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了微信小程序車牌號碼模擬鍵盤輸入功能的實(shí)現(xiàn)代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11javascript將16進(jìn)制的字符串轉(zhuǎn)換為10進(jìn)制整數(shù)hex
這篇文章主要介紹了javascript將16進(jìn)制的字符串轉(zhuǎn)換為10進(jìn)制整數(shù)hex,需要的朋友可以參考下2020-03-03基于Web?Components實(shí)現(xiàn)一個(gè)日歷原生組件
這篇文章主要為大家詳細(xì)介紹了如何利用Web?Components實(shí)現(xiàn)一個(gè)簡單的日歷原生組件,文中的示例代碼講解詳細(xì),需要的小伙伴可以了解一下2023-07-07