如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框


Posted in Javascript onMarch 01, 2016

本文我将为大家介绍Bootstrap中的弹出窗口组件Modal,此组件简单易用,效果大气漂亮且很实用!

由于浏览器提供的alert和confirm框体验不好,而且浏览器没有提供一个标准的以对话框的形式显示自定义HTML的弹框函数,所以很多项目都会自定义对话框组件。本篇文章介绍自己在项目中基于bootstrap的modal组件,自定义alert,confirm和modal对话框的经验,相对比较简单实用,希望能对你有所参考价值。

1. 实例展示

详细的代码可通过前面给出的下载链接下载源码去了解,代码量不大,这三个组件加起来只有200多行

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

如果你有javascript的组件开发经验,我这个层级的代码相信你一下子就能看明白。源码中我还给出了一个demo,这个demo模拟了一个比较贴近现实需求的一个场景:

1)用户点击界面上的某个按钮,打开之前定义的一个modal框:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

2)用户在打开的modal框内填写一些表单,点击确定的时候,会触发一些校验:

没填email时:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

填写了email之后:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

这两个提示其实是为了演示Alert和Confirm的效果硬塞进去的,实际上可能没有这么别扭的功能。

3)在提示Password为空的时候,细心的人会发现那个确定按钮处于一个禁用的状态,这个考虑是因为确定按钮最终要完成的是一些异步任务,在异步任务成功完成之前,我希望modal组件都不要关闭,并且能够控制已点击的按钮不能重复点击;

4)我用setTimeout模拟了一个异步任务,这个异步任务在点击确定按钮之后,3s才会回调,并且:

当email输入admin@admin 的时候,会给出提交成功的提示,确定之后就会关闭所有的弹框:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

当email输入其它值得时候,会给出提交失败的提示,并且modal框会依然显示在那里:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

在组件定义里面,尤其是注册按钮这一块,我加了一些AOP编程的处理,同时利用了jquery的延迟对象,来实现我需要的异步编程,详情请阅读源码,有问题可以在评论区交流赐教。

2. 组件需求

有时候为了写一个好用的组件,只需要把它的大概原型和要对外部提供的接口确定下来,就已经完成这个组件编写最重要的工作了,虽然还没有开始编码。以本文要编写的这几个组件来说,我想要的这几个组件的原型和调用形式分别是这样的:

1)自定义alert框

原型是:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

调用时最多需要两个参数,一个msg用来传递要显示的提示内容,一个onOk用来处理确定按钮点击时候的回调,调用形式有以下2种:

//1
Alert('您选择的订单状态不符合当前操作的条件,请刷新列表显示最新数据后再继续操作!');
//2
Alert({
msg: '您选择的订单状态不符合当前操作的条件,请刷新列表显示最新数据后再继续操作!',
onOk: function(){
}
});

第一种是没有回调的情况,那么直接传递msg即可,第二种是有回调的情况,用options对象的方式来传递msg和onOks回调这两个参数。不管onOk回调有没有,点击按钮的时候都要关闭弹框。

2)自定义confirm框

这个框的原型跟alert框只差一个按钮:

调用形式只有一种:

如何使用Bootstrap的modal组件自定义alert,confirm和modal对话框

Confirm({
msg: '您选择的订单状态不符合当前操作的条件,请确认是否要继续操作!',
onOk: function(){
},
onCancel: function(){
}
});

onCancel是在点击取消按钮时候的回调。不管onOk和onCancel回调有没有,点击按钮的时候都要关闭弹框。onCancel回调可以没有。

3)自定义modal框

原型:

如何使用Bootstrap的modal组件自定义alert,confirm和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组件过程中的一些方法和实践,代码偏多,不容易引起人的阅读兴趣,但是文中介绍的方法比较简单,而且这三个组件我已经用到好几个项目里面,从目前来看,能够解决我所有需要的弹框需求,所以我把它推荐出来,希望能给有需要的人带来帮助。

Javascript 相关文章推荐
js ondocumentready onmouseover onclick onmouseout 样式
Jul 22 Javascript
JQuery的AJAX实现文件下载的小例子
May 15 Javascript
JS+CSS实现简易实用的滑动门菜单效果
Sep 18 Javascript
跟我学习javascript的垃圾回收机制与内存管理
Nov 23 Javascript
jQuery实现弹出带遮罩层的居中浮动窗口效果
Sep 12 Javascript
分享一道关于闭包、bind和this的面试题
Feb 20 Javascript
JavaScript无缝滚动效果的实例代码
Mar 27 Javascript
JS获取url参数,JS发送json格式的POST请求方法
Mar 29 Javascript
ios设备中angularjs无法改变页面title的解决方法
Sep 13 Javascript
Vue动态生成表格的行和列
Jul 18 Javascript
js实现多个标题吸顶效果
Jan 08 Javascript
Node.js 深度调试方法解析
Jul 28 Javascript
JavaScript html5 canvas绘制时钟效果
Mar 01 #Javascript
javascript使用Promise对象实现异步编程
Mar 01 #Javascript
javascript html5实现表单验证
Mar 01 #Javascript
javascript中FOREACH数组方法使用示例
Mar 01 #Javascript
JS实现Select的option上下移动的方法
Mar 01 #Javascript
angular2使用简单介绍
Mar 01 #Javascript
JS实现的在线调色板实例(附demo源码下载)
Mar 01 #Javascript
You might like
多php服务器实现多session并发运行
2006/10/09 PHP
php中读写文件与读写数据库的效率比较分享
2013/10/19 PHP
PHP利用正则表达式将相对路径转成绝对路径的方法示例
2017/02/28 PHP
javascript 面向对象编程 万物皆对象
2009/09/17 Javascript
jquery实现人性化的有选择性禁用鼠标右键
2014/06/30 Javascript
Mac OS X 系统下安装和部署Egret引擎开发环境
2014/09/03 Javascript
jQuery老黄历完整实现方法
2015/01/16 Javascript
jQuery读取XML文件内容的方法
2015/03/09 Javascript
javascript动态设置样式style实例分析
2015/05/13 Javascript
轻松学习Javascript闭包函数
2015/12/15 Javascript
JavaScript html5 canvas绘制时钟效果
2016/03/01 Javascript
Angularjs 滚动加载更多数据
2016/03/17 Javascript
从零学习node.js之详解异步控制工具async(八)
2017/02/27 Javascript
JS传参及动态修改页面布局
2017/04/13 Javascript
jQuery遍历节点方法汇总(推荐)
2017/05/13 jQuery
nodejs利用ajax实现网页无刷新上传图片实例代码
2017/06/06 NodeJs
AngularJS 最常用的八种功能(基础知识)
2017/06/26 Javascript
详解webpack和webpack-simple中如何引入css文件
2017/06/28 Javascript
js实现黑白div块画空心的图形
2018/12/13 Javascript
vue 使用axios 数据请求第三方插件的使用教程详解
2019/07/05 Javascript
JS+css3实现幻灯片轮播图
2020/08/14 Javascript
在js文件中引入(调用)另一个js文件的三种方法
2020/09/11 Javascript
python计算程序开始到程序结束的运行时间和程序运行的CPU时间
2013/11/28 Python
python创建线程示例
2014/05/06 Python
python检测是文件还是目录的方法
2015/07/03 Python
Python绘制频率分布直方图的示例
2019/07/08 Python
Python Handler处理器和自定义Opener原理详解
2020/03/05 Python
机关党员进社区活动总结
2014/07/05 职场文书
婚礼女方父母答谢词
2015/01/04 职场文书
伏羲庙导游词
2015/02/09 职场文书
管辖权异议上诉状
2015/05/23 职场文书
热血教师观后感
2015/06/10 职场文书
班级联欢会主持词
2015/07/03 职场文书
2016年4月份红领巾广播稿
2015/12/21 职场文书
《草船借箭》教学反思
2016/02/23 职场文书
PostgreSQL将数据加载到buffer cache中操作方法
2021/04/16 PostgreSQL