5.9.3 源码分析

因为功能简单,所以源码自然就简单。在按钮插件里,源码主要就体现在如下几个方面:

❑toogle的特殊处理。

❑其他自定义参数的特殊处理。

❑jQuery的fn插件定义。

❑初始化绑定。

步骤1 立即调用的函数。此步骤与Modal插件的步骤1一样,此处不赘述。

步骤2 插件核心代码。首先是button插件类及原型方法的定义,主要源码如下:

  1. // button插件类及原型方法的定义
  2. var Button = function (element, options) { // 传入要触发的元素和调用options选项参数
  3. this.$element = $(element)
  4. this.options = $.extend({}, Button.DEFAULTS, options) // 合并参数
  5. this.isLoading = false // 是否加载状态
  6. }
  7. Button.DEFAULTS = {
  8. loadingText: 'loading...' // 默认loading时的文本内容
  9. }
  10.  
  11. // 设置按钮状态的方法,所有的自定义参数都使用这个方法
  12. Button.prototype.setState = function (state) {};
  13. // 切换按钮状态
  14. Button.prototype.toggle = function (){};

setState方法是最关键的,主要是根据传入的参数state(例如loading),来进行相关的设置。详细源码如下:

  1. // 设置按钮状态的方法
  2. Button.prototype.setState = function (state) {
  3. var d = 'disabled' // 按钮需要禁用时使用它,先赋值一个临时变量
  4. var $el = this.$element // 当前元素
  5. var val = $el.is('input') ? 'val' : 'html'
  6. // 如果是input,则使用val获取值,否则使用html获取值
  7. var data = $el.data() // 获取当前元素的自定义属性,即所有以data-开头的属性
  8.  
  9. state = state + 'Text'
  10. // 组装下面需要用到的属性,比如传入了loading,则组装成loadingText,在下面就查找
  11. // data-loading-text属性值
  12.  
  13. if (!data.resetText) $el.data('resetText', $el[val]())
  14. // 如果data里不包含data-reset-text值,则将当前元素的值赋给data-reset-text临时存
  15. // 放,以便过后再恢复使用它
  16.  
  17. $el[val](data[state] || this.options[state])
  18. // 给元素赋新值,先从自定义属性里查询(比如data-complete-text),如果没有,就从options
  19. // 默认值里查询
  20.  
  21. // 不阻止事件,以允许表单的提交
  22. setTimeout($.proxy(function () {
  23. if (state == 'loadingText') { // 如果传入的是loading,
  24. this.isLoading = true // 设置加载状态为true
  25. $el.addClass(d).attr(d, d) // 禁用该元素(即添加disabled样式和disabled属性)
  26. } else if (this.isLoading) {
  27. this.isLoading = false
  28. $el.removeClass(d).removeAttr(d)
  29. // 如果不是loading,则删除disabled样式和disabled属性
  30. }
  31. }, this), 0)
  32. }
  33.  
  34. // 切换按钮状态
  35. Button.prototype.toggle = function () {
  36. var changed = true // 设置change标记
  37. var $parent = this.$element.closest('[data-toggle="buttons"]')
  38. // 查找带有[data-toggle="buttons"]属性的最近父元素
  39.  
  40. if ($parent.length) { // 如果父元素存在
  41. var $input = this.$element.find('input') // 查看触发元素内是否有input
  42. if ($input.prop('type') == 'radio') { // 如果查找到的input是radio
  43. if ($input.prop('checked') && this.$element.hasClass('active')) {
  44. changed = false // 判断如果该radio已经是高亮且是被选中的,则不需
  45. // 要再改变(change为false)
  46. }else{ // 否则,查找同级元素是否有为active的,如果有,删除之
  47. $parent.find('.active').removeClass('active')
  48. }
  49. }
  50. if (changed) $input.prop('checked', !this.$element.hasClass('active')).
  51. trigger('change')
  52. // 如果有,继续判断是否活动(有active样式),否则的话则设置为禁用;不活动,就设置为启用,
  53. // 并触发change事
  54. }
  55.  
  56. if (changed) this.$element.toggleClass('active')
  57. // 在change标记为true时,将自身元素的状态进行反转,即反转active样式
  58. }

setState原型方法主要是结合参数,组合xxxText这样的名称,然后设置自定义属性(data-xxx-text),最后再调整disabled状态和样式,而toggle就不多说了。

步骤3 jQuery插件定义。具体代码如下:

  1. // 在jQuery上定义alert插件,并重设插件构造器
  2. var old = $.fn.button
  3. // 保留其他库的$.fn.button代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
  4. $.fn.button = function (option) {
  5. return this.each(function () { // 遍历所有符合规则的元素
  6. var $this = $(this)
  7. var data = $this.data('bs.button') // 获取自定义属性data-bs.button的值
  8. // (其实是button实例)
  9.  
  10. // 如果option是对象,说明是参数集合,在new Button的时候传入
  11. var options = typeof option == 'object' && option
  12.  
  13. // 如果没有button实例,就初始化一个,并传入this
  14. if (!data) $this.data('bs.button', (data = new Button(this, options)))
  15.  
  16. // 如果option是toggle字符,说明传入的是一个方法,直接调用该方法
  17. // 否则调用setState方法
  18. if (option == 'toggle') data.toggle()
  19. else if (option) data.setState(option)
  20. })
  21. }
  22. $.fn.button.Constructor = Button // 并重设插件构造器,可以通过该属性获取插件的真实类函数

步骤4 防冲突处理。此步骤与Modal插件的步骤4一样,此处不赘述。

步骤5 绑定触发事件。绑定触发事件的源码如下:

  1. // 绑定触发事件
  2. // 查询所有以button开头为值的data-toggle属性,绑定click事件
  3. $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
  4. var $btn = $(e.target) // 当前单击对象
  5. if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
  6. // 如果没有btn样式,就查找最近带有btn样式的元素
  7. $btn.button('toggle') // 反转状态
  8. e.preventDefault() // 组织默认事件
  9. })

从上述代码中可以看到,该初始化是设置了toggle参数,也就说只提供了状态反转功能,没有提供loading这样的功能。

另外还有一个问题,在上述代码中查询元素用选择符[data-toggle^=button]的时候,其意思是指data-toggle属性,并且其值是以button开头(比如声明式用法里的data-toggle="button"和data-toggle="buttons"),这就有了一个隐性的bug,如果自己想定义别的插件,比如data-toggle="buttonABC",就会受这个插件的影响,而导致出错。所以还是选项卡插件的方式比较好,如下面的示例。

笔者给出的建议代码如下:

  1. $(document).on('click.bs.button.data-api ', '[data-toggle="button"], [data-
  2. toggle="buttons"]', function (e) {
  3. // 处理逻辑
  4. })