如何使用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 相关文章推荐
javascript options属性集合操作代码
Dec 28 Javascript
Javascript Function对象扩展之延时执行函数
Jul 06 Javascript
javaScript实现浮点数转十六进制字符
Oct 29 Javascript
jquery如何判断某元素是否具备指定的样式
Nov 05 Javascript
jQuery 计算iframe 窗口大小的方法
May 13 Javascript
JavaScript设计模式之代理模式介绍
Dec 28 Javascript
JavaScript中的函数声明和函数表达式区别浅析
Mar 27 Javascript
JavaScript操作URL的相关内容集锦
Oct 29 Javascript
jqGrid表格应用之新增与删除数据附源码下载
Dec 02 Javascript
微信小程序 图片宽度自适应的实现
Apr 06 Javascript
JS 设置Cookie 有效期 检测cookie
Jun 15 Javascript
Vue.js结合Ueditor富文本编辑器的实例代码
Jul 11 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
无线电波是什么?它是怎样传输的?
2021/03/01 无线电
在PHP里得到前天和昨天的日期的代码
2007/08/16 PHP
PHP可变函数的使用详解
2013/06/14 PHP
PHP中类型转换 ,常量,系统常量,魔术常量的详解
2017/10/26 PHP
laravel返回统一格式错误码问题
2019/11/04 PHP
PHP实现长轮询消息实时推送功能代码实例讲解
2021/02/26 PHP
Extjs4 关于Store的一些操作(加载/回调/添加)
2013/04/18 Javascript
动态加载script文件的两种方法
2013/08/15 Javascript
jQuery使用ajaxSubmit()提交表单示例
2014/04/04 Javascript
jQuery实现自定义事件的方法
2015/04/17 Javascript
js显示文本框提示文字的方法
2015/05/07 Javascript
js中flexible.js实现淘宝弹性布局方案
2020/06/23 Javascript
简单理解vue中实例属性vm.$els
2016/12/01 Javascript
js返回顶部实例分享
2016/12/21 Javascript
JS实现的邮箱提示补全效果示例
2018/01/30 Javascript
Bootstrap Table实现定时刷新数据的方法
2018/08/13 Javascript
记录微信小程序 height: calc(xx - xx);无效问题
2019/12/30 Javascript
python 异常处理总结
2016/10/18 Python
详解用TensorFlow实现逻辑回归算法
2018/05/02 Python
python使用插值法画出平滑曲线
2018/12/15 Python
浅谈Pandas Series 和 Numpy array中的相同点
2019/06/28 Python
Tensorflow累加的实现案例
2020/02/05 Python
HTML5 Canvas绘制圆点虚线实例
2015/01/01 HTML / CSS
EVE LOM英国官网:全世界最好的洁面膏
2017/10/30 全球购物
伦敦剧院门票:From The Box Office
2018/06/30 全球购物
师范毕业生自我鉴定
2014/01/15 职场文书
《春到梅花山》教学反思
2014/04/16 职场文书
总经理任命书范本
2014/06/05 职场文书
市场调查策划方案
2014/06/10 职场文书
2014年度党员自我评议
2014/09/13 职场文书
2014县政府领导班子对照检查材料思想汇报
2014/09/25 职场文书
护理专业自荐信范文
2015/03/06 职场文书
校园环境卫生倡议书
2015/04/29 职场文书
离婚承诺书格式范文
2015/05/04 职场文书
小学班主任研修日志
2015/11/13 职场文书
2016教师国培研修感言
2015/12/08 职场文书