In this article, I will introduce to you the pop-up window component Modal in Bootstrap. This component is simple and easy to use, has beautiful effects and is very practical!
Since the alert and confirm boxes provided by the browser have a poor experience, and the browser does not provide a standard pop-up function that displays custom HTML in the form of a dialog box, many projects will customize dialog components. This article introduces my experience in customizing alert, confirm and modal dialog boxes based on bootstrap modal components in the project. It is relatively simple and practical. I hope it can be of reference value to you.
1. Example display
The detailed code can be understood by downloading the source code from the download link given above. The amount of code is not large. The three components add up to only more than 200 lines
If you have JavaScript component development experience, I believe you can understand my code at this level in no time. I also provide a demo in the source code, which simulates a scenario that is closer to real-life needs:
1) The user clicks a button on the interface to open a previously defined modal box:
2) The user fills in some forms in the opened modal box and clicks OK, some verification will be triggered:
When email is not filled in:
After filling in your email:
These two prompts are actually shoehorned in to demonstrate the effects of Alert and Confirm. In fact, they may not have such awkward functions.
3) When prompted that Password is empty, careful people will find that the OK button is in a disabled state. This consideration is because the OK button ultimately completes some asynchronous tasks. Before the asynchronous tasks are successfully completed, I I hope that the modal components will not be closed, and that the clicked button can be controlled from being clicked repeatedly;
4) I used setTimeout to simulate an asynchronous task. This asynchronous task will call back 3s after clicking the OK button, and:
When the email is entered as admin@admin, a successful submission prompt will be given. After confirmation, all pop-up boxes will be closed:
When the email enters other values, a submission failure prompt will be given, and the modal box will still be displayed there:
In the component definition, especially the registration button, I added some AOP programming processing and used jquery's delay object to achieve the asynchronous programming I needed. Please read the source code for details. If you have any questions, please leave a comment District exchanges and education.
2. Component requirements
Sometimes in order to write a useful component, you only need to determine its rough prototype and the interface to be provided to the outside. Then the most important work of writing the component has been completed, although the coding has not yet started. Taking the components to be written in this article as an example, the prototypes and calling forms of the components I want are as follows:
1) Custom alert box
The prototype is:
Up to two parameters are required when calling, one msg is used to pass the prompt content to be displayed, and one onOk is used to handle the callback when the OK button is clicked. There are two calling forms:
//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 === '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') { //异步任务只有在成功回调的时候关闭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组件过程中的一些方法和实践,代码偏多,不容易引起人的阅读兴趣,但是文中介绍的方法比较简单,而且这三个组件我已经用到好几个项目里面,从目前来看,能够解决我所有需要的弹框需求,所以我把它推荐出来,希望能给有需要的人带来帮助。