5.9.3 源码分析
因为功能简单,所以源码自然就简单。在按钮插件里,源码主要就体现在如下几个方面:
❑toogle的特殊处理。
❑其他自定义参数的特殊处理。
❑jQuery的fn插件定义。
❑初始化绑定。
步骤1 立即调用的函数。此步骤与Modal插件的步骤1一样,此处不赘述。
步骤2 插件核心代码。首先是button插件类及原型方法的定义,主要源码如下:
- // button插件类及原型方法的定义
- var Button = function (element, options) { // 传入要触发的元素和调用options选项参数
- this.$element = $(element)
- this.options = $.extend({}, Button.DEFAULTS, options) // 合并参数
- this.isLoading = false // 是否加载状态
- }
- Button.DEFAULTS = {
- loadingText: 'loading...' // 默认loading时的文本内容
- }
- // 设置按钮状态的方法,所有的自定义参数都使用这个方法
- Button.prototype.setState = function (state) {};
- // 切换按钮状态
- Button.prototype.toggle = function (){};
setState方法是最关键的,主要是根据传入的参数state(例如loading),来进行相关的设置。详细源码如下:
- // 设置按钮状态的方法
- Button.prototype.setState = function (state) {
- var d = 'disabled' // 按钮需要禁用时使用它,先赋值一个临时变量
- var $el = this.$element // 当前元素
- var val = $el.is('input') ? 'val' : 'html'
- // 如果是input,则使用val获取值,否则使用html获取值
- var data = $el.data() // 获取当前元素的自定义属性,即所有以data-开头的属性
- state = state + 'Text'
- // 组装下面需要用到的属性,比如传入了loading,则组装成loadingText,在下面就查找
- // data-loading-text属性值
- if (!data.resetText) $el.data('resetText', $el[val]())
- // 如果data里不包含data-reset-text值,则将当前元素的值赋给data-reset-text临时存
- // 放,以便过后再恢复使用它
- $el[val](data[state] || this.options[state])
- // 给元素赋新值,先从自定义属性里查询(比如data-complete-text),如果没有,就从options
- // 默认值里查询
- // 不阻止事件,以允许表单的提交
- setTimeout($.proxy(function () {
- if (state == 'loadingText') { // 如果传入的是loading,
- this.isLoading = true // 设置加载状态为true
- $el.addClass(d).attr(d, d) // 禁用该元素(即添加disabled样式和disabled属性)
- } else if (this.isLoading) {
- this.isLoading = false
- $el.removeClass(d).removeAttr(d)
- // 如果不是loading,则删除disabled样式和disabled属性
- }
- }, this), 0)
- }
- // 切换按钮状态
- Button.prototype.toggle = function () {
- var changed = true // 设置change标记
- var $parent = this.$element.closest('[data-toggle="buttons"]')
- // 查找带有[data-toggle="buttons"]属性的最近父元素
- if ($parent.length) { // 如果父元素存在
- var $input = this.$element.find('input') // 查看触发元素内是否有input
- if ($input.prop('type') == 'radio') { // 如果查找到的input是radio
- if ($input.prop('checked') && this.$element.hasClass('active')) {
- changed = false // 判断如果该radio已经是高亮且是被选中的,则不需
- // 要再改变(change为false)
- }else{ // 否则,查找同级元素是否有为active的,如果有,删除之
- $parent.find('.active').removeClass('active')
- }
- }
- if (changed) $input.prop('checked', !this.$element.hasClass('active')).
- trigger('change')
- // 如果有,继续判断是否活动(有active样式),否则的话则设置为禁用;不活动,就设置为启用,
- // 并触发change事
- }
- if (changed) this.$element.toggleClass('active')
- // 在change标记为true时,将自身元素的状态进行反转,即反转active样式
- }
setState原型方法主要是结合参数,组合xxxText这样的名称,然后设置自定义属性(data-xxx-text),最后再调整disabled状态和样式,而toggle就不多说了。
步骤3 jQuery插件定义。具体代码如下:
- // 在jQuery上定义alert插件,并重设插件构造器
- var old = $.fn.button
- // 保留其他库的$.fn.button代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
- $.fn.button = function (option) {
- return this.each(function () { // 遍历所有符合规则的元素
- var $this = $(this)
- var data = $this.data('bs.button') // 获取自定义属性data-bs.button的值
- // (其实是button实例)
- // 如果option是对象,说明是参数集合,在new Button的时候传入
- var options = typeof option == 'object' && option
- // 如果没有button实例,就初始化一个,并传入this
- if (!data) $this.data('bs.button', (data = new Button(this, options)))
- // 如果option是toggle字符,说明传入的是一个方法,直接调用该方法
- // 否则调用setState方法
- if (option == 'toggle') data.toggle()
- else if (option) data.setState(option)
- })
- }
- $.fn.button.Constructor = Button // 并重设插件构造器,可以通过该属性获取插件的真实类函数
步骤4 防冲突处理。此步骤与Modal插件的步骤4一样,此处不赘述。
步骤5 绑定触发事件。绑定触发事件的源码如下:
- // 绑定触发事件
- // 查询所有以button开头为值的data-toggle属性,绑定click事件
- $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
- var $btn = $(e.target) // 当前单击对象
- if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
- // 如果没有btn样式,就查找最近带有btn样式的元素
- $btn.button('toggle') // 反转状态
- e.preventDefault() // 组织默认事件
- })
从上述代码中可以看到,该初始化是设置了toggle参数,也就说只提供了状态反转功能,没有提供loading这样的功能。
另外还有一个问题,在上述代码中查询元素用选择符[data-toggle^=button]的时候,其意思是指data-toggle属性,并且其值是以button开头(比如声明式用法里的data-toggle="button"和data-toggle="buttons"),这就有了一个隐性的bug,如果自己想定义别的插件,比如data-toggle="buttonABC",就会受这个插件的影响,而导致出错。所以还是选项卡插件的方式比较好,如下面的示例。
笔者给出的建议代码如下:
- $(document).on('click.bs.button.data-api ', '[data-toggle="button"], [data-
- toggle="buttons"]', function (e) {
- // 处理逻辑
- })