5.5.3 源码分析

在源码分析之前,我们再来回顾以下Tab选项卡组件的原理要点:

❑单击一个元素时,首先将原来高亮(active)的元素取消高亮(即去除.active样式)。

❑然后给被单击元素添加.active样式,使之高亮。

❑如果该单击元素是下拉菜单里的子元素,则下拉菜单所在的父容器li也要高亮设置。

❑如果定义了动画,或者回调函数,就相应地进行执行。

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

步骤2 插件核心代码。主要是Tab核心类函数的定义、事件触发、新旧元素的高亮设置等。核心代码如下:

  1. var Tab = function (element) {
  2. this.element = $(element) // 指定当前元素
  3. }
  4. // 显示逻辑,触发show和shown事件
  5. Tab.prototype.show = function (){};
  6. // active样式的应用,面板的隐藏和显示,以及tab的高亮与反高亮都使用了该方法
  7. Tab.prototype.activate = function (){};

上面代码里的两个原型方法的作用分别如下:

❑show方法主要是触发show事件,然后调用activate原型方法,最后再触发shown事件。

❑activate用于取消原来元素的高亮设置,对现有元素进行高亮设置,然后触发动画和回调函数。

上述原型方法的详细源码如下:

  1. // 显示逻辑,触发show和shown事件
  2. Tab.prototype.show = function () {
  3. var $this = this.element // 当前tab
  4. var $ul = $this.closest('ul:not(.dropdown-menu)')
  5. // 找到最近的ul(但不是下拉菜单),其实就是父元素
  6. var selector = $this.data('target') // 找到当前tab上的data-target属性值
  7.  
  8. if (!selector) { // 如果data-target属性不存在
  9. selector = $this.attr('href') // 再查找href值
  10. selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // IE7特殊处理
  11. }
  12.  
  13. // 如果当前tab已经是活动状态了,即父元素li上有active样式的话,直接返回,不做任何处理
  14. if ($this.parent('li').hasClass('active')) return
  15.  
  16. // 找到上一个元素,即上一个带有active样式的li里的a元
  17. var previous = $ul.find('.active:last a')[0]
  18.  
  19. // 设置show事件,注意此时的relatedTarget是上一个元素(自定义回调时使用,见JavaScript用法里的示例)
  20. var e = $.Event('show.bs.tab', {
  21. relatedTarget: previous
  22. })
  23.  
  24. $this.trigger(e) // 触发show事件
  25.  
  26. if (e.isDefaultPrevented()) return // 如果自定义回调里阻止了默认行为,则不再继续处理
  27.  
  28. var $target = $(selector) // 要激活显示的面板,即target(或href)里的值所对应的元素
  29.  
  30. this.activate($this.parent('li'), $ul) // 高亮显示当前tab
  31. this.activate($target, $target.parent(), function () {
  32. // 显示对应的面板,并在回调里触发shown事件
  33. $this.trigger({
  34. type: 'shown.bs.tab',
  35. relatedTarget: previous
  36. })
  37. })
  38. }
  39. // active样式的应用,面板的隐藏和显示,以及tab的高亮与反高亮都使用了该方法
  40. Tab.prototype.activate = function (element, container, callback) {
  41.  
  42. // 查找当前容器(nav里的ul或者tab-content)里所有有active样式的元素
  43. var $active = container.find('> .active')
  44. var transition = callback
  45. && $.support.transition
  46. && $active.hasClass('fade') // 判断是使用回调还是动画
  47.  
  48. function next() {
  49. $active
  50. .removeClass('active') // 去除其他元素的active样式
  51. .find('> .dropdown-menu > .active')
  52. // 包括li元素里面的下拉菜单里的active样式也要去除
  53. .removeClass('active')
  54.  
  55. element.addClass('active') // 给当前被单击的元素添加active高亮样式
  56.  
  57. if (transition) {
  58. element[0].offsetWidth // 如果支持动画,就重绘页面
  59. element.addClass('in') // 并添加in样式,去除透明
  60. } else {
  61. element.removeClass('fade') // 否则删除fade
  62. }
  63.  
  64. if (element.parent('.dropdown-menu')) {
  65. // 如果单击的是下拉菜单里的颜色,则查找父元素(即ul)
  66. // 将离下拉菜单最近的li.dropdown元素(一般都是其父元素)进行高亮
  67. element.closest('li.dropdown').addClass('active')
  68. }
  69.  
  70. callback && callback() // 最后,如果有回调,就执行回调
  71.  
  72. }
  73.  
  74. transition ? // 如果支持动画
  75. $active
  76. .one($.support.transition.end, next) // 就在动画结束后,执行next
  77. .emulateTransitionEnd(150) :
  78. next() // 否则,直接执行next
  79.  
  80. $active.removeClass('in')
  81. }

步骤3 jQuery插件定义。是在jQuery上定义$.fn. tab插件,和Modal插件一样,在实例化之前,需要收集option参数。源码如下:

  1. var old = $.fn.tab
  2. // 保留其他库的$.fn.tab代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
  3. $.fn.tab = function (option) {
  4. return this.each(function () { // 根据选择器,遍历所有符合规则的元素
  5. var $this = $(this)
  6. var data = $this.data('bs.tab') // 查询当前元素上是否已经有了tab实例
  7. if (!data) $this.data('bs.tab', (data = new Tab(this)))
  8. // 如果没有,就初始化一个,并传入this
  9. if (typeof option == 'string') data[option]()
  10. // 如果option是字符,说明传入的是方法,直接调用该方法
  11. })
  12. }
  13. $.fn.tab.Constructor = Tab // 重设插件构造器,可以通过该属性获取插件的真实类函数

步骤4 防冲突处理。源码如下:

  1. // 防冲突处理
  2. $.fn.tab.noConflict = function () {
  3. $.fn.tab = old // 恢复以前的旧代码
  4. return this // 将$.fn.tab.noConflict()设置为Bootstrap的tab插件
  5. }

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

  1. // 绑定触发事件
  2. // 查询所有组合条件(带data-toggle="tab"或者data-toggle="pill"属性)的元素
  3. $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle=
  4. "pill"]', function (e) {
  5. e.preventDefault() // 阻止默认行为
  6. $(this).tab('show') // 在单击事件上触发tab组件的show方法
  7. })

如果要使用自己风格的data-toggle的话,就要自己写代码触发事件了。比如要使用一个自定义属性data-toggle="switch",那绑定代码就应该如下:

  1. $(document).on('click.bs.tab.data-api', '[data-toggle="switch"]', function (e) {
  2. e.preventDefault()
  3. $(this).tab('show')
  4. })

注意

从下一节开始,我们将忽略步骤4(防冲突)的叙述,因为所有的插件在这一步的代码都一样,只是换个名字而已。也就是说,以后的源码分析,将只分析步骤2(类和原型方法定义)、步骤3(fn插件制作)和步骤5(事件绑定)。