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

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


本文我将为大家介绍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种:

?

1

2

3

4

5

6

7

8

//1

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

//2

Alert({

msg: '您选择的订单状态不符合当前操作的条件,请刷新列表显示最新数据后再继续操作!',

onOk: function(){

}

});

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

2)自定义confirm框

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

调用形式只有一种:

?

1

2

3

4

5

6

7

Confirm({

msg: '您选择的订单状态不符合当前操作的条件,请确认是否要继续操作!',

onOk: function(){

},

onCancel: function(){

}

});

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

3)自定义modal框

原型:

调用形式:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

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中调用实例

接下来演示下我在完成这三个组件开发之后,实际使用过程中调用这些组件的方式:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

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之后,又会引发一个新问题,就是组件没有了遮罩效果,所以为了兼顾这两个问题,只能自己写一个简单的遮罩 处理:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

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)组件的选项的默认值定义

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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:

?

1

2

3

4

5

6

7

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)构造函数:

?

1

2

3

4

5

6

7

8

function ModalDialog(options) {

this.options = this.getOptions(options);

this.$modal = undefined;

this.$modalTitle = undefined;

this.$modalBody = undefined;

this.$modalFooter = undefined;

this.state = undefined;

}

这个主要是声明了用到的一些实例变量。

5)关键的原型方法和函数

?

1

2

3

4

5

6

open: function (state) {

this.state = state;

!this.$modal && initModal(this, this.options);

BackDrop.show();

this.$modal.modal('show');

}

这是个原型方法,组件的初始化也是在这个方法调用的时候执行的(延迟初始化)。

?

1

2

3

4

5

6

7

8

9

10

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源码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

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源码:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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,了解到这些基础之后,组件就可以这样定义:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
请分析以下代码中“编辑”按钮为什么没有用?<a href="#" onclick="cookClassEdit('<%=cookClass.getCookClassId() %>');"><i class="fa fa-pencil fa-fw"></i>编辑</a> <div id="cookClassEditDialog" class="modal fade" tabindex="-1" role="dialog"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title"><i class="fa fa-edit"></i> 烹饪分类信息编辑</h4> </div> <div class="modal-body" style="height:450px; overflow: scroll;"> <form class="form-horizontal" name="cookClassEditForm" id="cookClassEditForm" enctype="multipart/form-data" method="post" class="mar_t15"> <div class="form-group"> <label for="cookClass_cookClassId_edit" class="col-md-3 text-right">分类id:</label> <div class="col-md-9"> <input type="text" id="cookClass_cookClassId_edit" name="cookClass.cookClassId" class="form-control" placeholder="请输入分类id" readOnly> </div> </div> <div class="form-group"> <label for="cookClass_className_edit" class="col-md-3 text-right">分类名称:</label> <div class="col-md-9"> <input type="text" id="cookClass_className_edit" name="cookClass.className" class="form-control" placeholder="请输入分类名称"> </div> </div> <div class="form-group"> <label for="cookClass_cookClassDesc_edit" class="col-md-3 text-right">烹饪分类介绍:</label> <div class="col-md-9"> <textarea id="cookClass_cookClassDesc_edit" name="cookClass.cookClassDesc" rows="8" class="form-control" placeholder="请输入烹饪分类介绍"></textarea> </div> </div> </form> <style>#cookClassEditForm .form-group {margin-bottom:5px;} </style> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary" onclick="ajaxCookClassModify();">提交</button> </div> </div> </div> </div> function cookClassEdit(cookClassId) { $.ajax({ url : basePath + "CookClass/" + cookClassId + "/update", type : "get", dataType: "json", success : function (cookClass, response, status) { if (cookClass) { $("#cookClass_cookClassId_edit").val(cookClass.cookClassId); $("#cookClass_className_edit").val(cookClass.className); $("#cookClass_cookClassDesc_edit").val(cookClass.cookClassDesc); $('#cookClassEditDialog').modal('show'); } else { alert("获取信息失败!"); } } }); } function ajaxCookClassModify() { $.ajax({ url : basePath + "CookClass/" + $("#cookClass_cookClassId_edit").val() + "/update", type : "post", dataType: "json", data: new FormData($("#cookClassEditForm")[0]), success : function (obj, response, status) { if(obj.success){ alert("信息修改成功!"); location.href= basePath + "CookClass/frontlist"; }else{ alert(obj.message); } }, processData: false, contentType: false, }); }
06-03

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值