6.2.2 Info弹窗扩展
首先,我们先来明确一下UI的设计。Info弹窗和普通弹窗的唯一不同点是:Info弹窗里有“确认”和“取消”按钮,分别对应确认和取消操作。相应的HTML部分如下:
- <div class="modal-footer">
- <button type="button" class="btn btn-success">确认</button>
- <button type="button" class="btn btn-danger">取消</button>
- </div>
在普通的Modal弹窗里,如果我们要实现Info弹窗效果,就需要在“确认”和“取消”按钮上分别绑定事件,而且如果Modal内容是从远程(remote)获取的,那只能在shown.bs.modal的回调里绑定上述两个按钮的click事件,然后再处理相应的逻辑。所以,这也就意味着,要为这两个按钮分别添加id属性。
通过对data-dismiss="modal"的了解可以看出,只需要以类似的方式,给按钮声明一个自定义属性,然后在新的Info弹窗里处理上述两个按钮的单击行为就可以了,然后再暴露两个事件回调出来。自定义属性的修改如下:
- <div class="modal-footer">
- <button type="button" class="btn btn-success" data-info="confirm">确认</button>
- <button type="button" class="btn btn-danger" data-info="cancel">取消</button>
- </div>
添加一个data-info按钮,在弹窗显示的时候,绑定事件,然后执行操作(处理回调),整个思路就是这样的。也就是说,声明式用法里只多了两个自定义属性,其他的和Modal插件一模一样,我们就一步一步地在Modal插件基础上扩展吧。
首先,我们将自己新扩展的插件取名为InfoModal,所以要先定义整个插件类函数:
- // InfoModal类定义
- var InfoModal = function (element, options) {
- this.$element = $(element);
- this.super = this.$element.data('bs.modal'); // 获取自定义属性bs.modal的值
- this.options = options;
- this.$element.on('click.confirm.infomodal', '[data-info="confirm"]',
- $.proxy(this.confirm, this));
- this.$element.on('click.cancel.infomodal', '[data-info="cancel"]',
- $.proxy(this.cancel, this));
- var that = this; // 防止污染作用域,用临时变量that
- }
- // 默认设置
- InfoModal.DEFAULTS = {
- closeAfterConfirm: false,
- closeAfterCancel: true
- }
上述代码和Modal类似,但有以下几个地方不一样:
1)多了一个this.super对象,它的值指向前面所说的Modal弹窗的示例,以便可以使用该实例上的一些原型方法。
2)在modal元素内部的子元素(带有data-info属性)上分别绑定了confirm和cancel方法,以便在单击的时候调用它们。
3)还定义了两个默认值,分别设置在单击完“确认”和“取消”按钮后,使用的过程中可以随意设置要不要自动关闭,也可以不关闭(利用回调函数手动进行关闭)。
基本的类定义完了,开始定义原型方法和自定义事件。要思考到底要定义多少原型方法:首先confirm和cancel肯定得要,其次show、hide和toggle这3个也应该要。而对于自定义事件,首先要有和Modal弹窗一样的show、shown、hide、hidden事件,另外,又添加了confirm.bs.infomodal和cancel.bs.infomodal事件。
先来定义原型方法:
- // 反转弹窗状态
- InfoModal.prototype.toggle = function (_relatedTarget) {
- // 如果是关闭状态,则打开弹窗,否则关闭
- return this[!this.super.isShown ? 'show' : 'hide'](_relatedTarget)
- }
- // 打开弹窗
- InfoModal.prototype.show = function (_relatedTarget) {
- this.super.show(_relatedTarget);
- }
- // 关闭弹窗
- InfoModal.prototype.hide = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- this.super.hide();
- }
- // 单击“确认”按钮的行为
- InfoModal.prototype.confirm = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- var e = $.Event('confirm.bs.infomodal');
- this.$element.trigger(e); // 确认前触发事件,主要用于处理相关代码
- if (e.isDefaultPrevented()) return;
- if (this.options.closeAfterConfirm) {
- this.hide(); // 如果设置了data-close-after-confirm=true参数,
- // 则关闭弹窗
- }
- }
- // 单击“取消”按钮的行为
- InfoModal.prototype.cancel = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- var e = $.Event('cancel.bs.infomodal');
- this.$element.trigger(e); // 取消前先触发事件
- if (e.isDefaultPrevented()) return;
- if (this.options.closeAfterCancel) {
- this.hide(e); // 如果设置了data-close-after-cancel=true参数,
- // 则关闭弹窗
- }
- }
上述几个原型方法,首先是利用modal实例(this.super)里已有的方法,然后在confirm和cancel里分别触发了所对应的自定义事件(其实两个方法里也没处理什么逻辑,主要就是触发事件)。
笔者在这里是通过绑定modal现有的事件来触发这些新的事件。代码的位置放置在InfoModal类里的最底部。代码如下:
- var that = this; // 防止污染作用域,用临时变量that
- this.$element.on("show.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('show.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("shown.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('shown.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("hide.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('hide.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("hidden.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('hidden.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
也就是,modal触发自己的事件时,就会利用这些回调触发InfoModal类的事件,而且这样做有个好处:那就是我如果阻塞了事件(调用e.preventDefault()),modal后续就不能再触发了。所以如果分别对这8个事件进行定义,它们的执行顺序应该如下:
❑show.bs.modal
❑show.bs.infomodal
❑shown.bs.modal
❑shown.bs.infomodal
❑hide.bs.modal
❑hide.bs.infomodal
❑hidden.bs.modal
❑hidden.bs.infomodal
读者可能会问:不在show和hide方法里,是否也可以触发新的show、shown、hide、hidden自定义事件呢?这是可以的,大家可以自行试验。
另外,由于这种机制是因为对新的事件进行绑定,肯定得先触发Modal的事件,才能触发Infomodal的事件。如果想要改变show.bs.modal和show.bs.infomodal的执行顺序,也必须在新的show和hide原型方法里改变触发顺序的设置,大家可以自行试验一下。
在jQuery.fn.infomodal插件定义的时候,和modal几乎一样,除了要把对应的名字全换成InfoModal以外,唯一需要注意的就是,InfoModal默认应该是关闭的,每次触发时都应该打开,也就是说原来定义的toggle方法基本上用不上。具体代码如下:
- $.fn.infomodal = function (option, _relatedTarget) {
- return this.each(function () {
- // 根据选择器,遍历所有符合规则的元素
- var $this = $(this)
- var data = $this.data('bs.infomodal') // 获取自定义属性bs.infomodal的值
- var options = $.extend({}, InfoModal.DEFAULTS, $this.data(),
- typeof option == 'object' && option)
- // 将默认参数、选择器所在元素的自定义属性(data-开头)和option参数,
- // 这3种的值合并在一起,作为options参数
- // 优先级:后面的参数优先级高于前面的参数
- options.show = false; // 默认先关闭,然后在后面手动打开
- var modal = $this.modal(options, _relatedTarget);
- if (!data) $this.data('bs.infomodal', (data = new InfoModal(this, options)))
- // 如果值不存在,则将InfoModal实例设置为bs.infomodal的值
- if (typeof option == 'string') {
- data[option](_relatedTarget)
- }
- else {
- data.show(_relatedTarget);
- }
- // 如果option传递了string,则表示要执行某个方法
- // 比如传入了show,则要执行InfoModal实例的show方法,data["show"]相当于data.show()
- })
- }
其他关于防冲突的和DATA-API的,和modal除了名字不一样,其他都一样(InfoModal是通过[data-toggle="infomodal"]触发的)。运行效果如图6-3所示。
图6-3 Info弹窗运行效果
使用时的示例代码如下:
- <div class="modal fade" id="DisabledPopup" data-close-after-confirm="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">... </div>
- <div class="modal-body">... </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-success" data-info="confirm">
- 确认</button>
- <button type="button" class="btn btn-danger" data-info="cancel">
- 取消</button>
- </div>
- </div>
- </div>
- </div>
- <button class="btn btn-warning" data-toggle="infomodal" data-target="#DisabledPopup"
- data-backdrop="static">Disable</button>
由示例代码也可以看到,我们没有改动原有modal的任何内容,只是添加了一些自定义属性。同样的道理,InfoModal既支持声明式用法,也支持JavaScript用法,具体的实例读者可以自行试验。完整的源代码如下:
- +function ($) {
- "use strict";
- // InfoModal类定义
- var InfoModal = function (element, options) {
- this.$element = $(element);
- this.super = this.$element.data('bs.modal');
- // 获取自定义属性bs.modal的值
- this.options = options;
- this.$element.on('click.confirm.infomodal', '[data-info="confirm"]',
- $.proxy(this.confirm, this));
- this.$element.on('click.cancel.infomodal', '[data-info="cancel"]',
- $.proxy(this.cancel, this));
- var that = this; // 防止污染作用域,用临时变量that
- this.$element.on("show.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('show.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("shown.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('shown.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("hide.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('hide.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- this.$element.on("hidden.bs.modal", function (e) {
- that.$element.trigger(e = $.Event('hidden.bs.infomodal'));
- if (e.isDefaultPrevented()) return;
- });
- }
- // 默认设置
- InfoModal.DEFAULTS = {
- closeAfterConfirm: false,
- closeAfterCancel: true
- }
- // 反转弹窗状态
- InfoModal.prototype.toggle = function (_relatedTarget) {
- return this[!this.super.isShown ? 'show' : 'hide'](_relatedTarget)
- // 如果是关闭状态,则打开弹窗,否则关闭
- }
- // 打开弹窗
- InfoModal.prototype.show = function (_relatedTarget) {
- this.super.show(_relatedTarget);
- }
- // 关闭弹窗
- InfoModal.prototype.hide = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- this.super.hide();
- }
- // 单击“确认”按钮的行为
- InfoModal.prototype.confirm = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- var e = $.Event('confirm.bs.infomodal');
- this.$element.trigger(e); // 确认前触发事件,主要用于处理相关代码
- if (e.isDefaultPrevented()) return;
- if (this.options.closeAfterConfirm) {
- this.hide(e); // 如果设置了data-close-after-confirm=true
- // 参数,则关闭弹窗
- }
- }
- // 单击“取消”按钮的行为
- InfoModal.prototype.cancel = function (e) {
- if (e) e.preventDefault(); // 先阻止冒泡行为
- var e = $.Event('cancel.bs.infomodal');
- this.$element.trigger(e); // 取消前先触发事件
- if (e.isDefaultPrevented()) return;
- if (this.options.closeAfterCancel) {
- this.hide(e); // 如果设置了data-close-after-cancel=true
- // 参数,则关闭弹窗
- }
- }
- // InfoModal 插件定义
- var old = $.fn.infomodal
- // 如果定义了其他infomodal,则保留它(以便在noConflict(解决防冲突)之后,
- // 可以继续使用该旧插件代码)
- $.fn.infomodal = function (option, _relatedTarget) {
- return this.each(function () {
- // 根据选择器,遍历所有符合规则的元素
- var $this = $(this)
- var data = $this.data('bs.infomodal')
- // 获取自定义属性bs.infomodal的值
- var options = $.extend({}, InfoModal.DEFAULTS, $this.data(),
- typeof option == 'object' && option)
- // 将默认参数、选择器所在元素的自定义属性(data-开头)、option参数这3种的值合
- // 并在一起,作为options参数
- // 优先级:后面的参数优先级高于前面的参数
- options.show = false; // 默认先关闭,然后在后面手动打开
- var modal = $this.modal(options, _relatedTarget);
- if (!data) $this.data('bs.infomodal', (data = new InfoModal(this, options)))
- // 如果值不存在,则将InfoModal实例设置为bs.infomodal的值
- // 如果option传递了string,则表示要执行某个方法
- // 比如传入了show,则要执行InfoModal实例的show方法,data["show"]相当于data.show();
- if (typeof option == 'string') {
- data[option](_relatedTarget)
- }
- else {
- data.show(_relatedTarget);
- }
- })
- }
- $.fn.infomodal.Constructor = InfoModal;
- // 重设插件构造器,可以通过该属性获取插件的真实类函数
- // InfoModal防冲突
- $.fn.infomodal.noConflict = function () {
- $.fn.infomodal = old
- return this
- }
- // InfoModal DATA-API
- $(document).on('click.bs.infomodal.data-api', '[data-toggle="infomodal"]',
- function (e) {
- // 监测所有拥有自定义属性data-toggle="modal"的元素上的单击事件
- var $this = $(this)
- var href = $this.attr('href') // 获取href属性值
- var $target = $($this.attr('data-target') || (href &&
- href.replace(/.*(?=#[^\s]+$)/, '')))
- // 获取data-target属性值,如果没有,则获取href值,该值是所弹出元素的id
- var option = $target.data('infomodal') ? 'toggle' : $.extend
- ({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
- // 如果弹窗元素上设置了data-infomodal属性值,则option值是字符串toggle
- // 否则将remote值(如果有)、弹窗元素上的自定义属性值集合、触发元素上的自定义属性
- // 值集合进行合并,作为option选项对象
- e.preventDefault() // 阻止默认行为
- $target
- .infomodal(option, this)
- // 给弹窗元素绑定infomodal插件(也就是实例化InfoModal),并传入option参数
- .one('hide', function () {
- $this.is(':visible') && $this.focus()
- // 定义一次hide事件,给所单击元素加上焦点
- })
- })
- }(jQuery);