5.5.3 源码分析
在源码分析之前,我们再来回顾以下Tab选项卡组件的原理要点:
❑单击一个元素时,首先将原来高亮(active)的元素取消高亮(即去除.active样式)。
❑然后给被单击元素添加.active样式,使之高亮。
❑如果该单击元素是下拉菜单里的子元素,则下拉菜单所在的父容器li也要高亮设置。
❑如果定义了动画,或者回调函数,就相应地进行执行。
步骤1 立即调用的函数。此步骤与Modal插件一样,此处不赘述。
步骤2 插件核心代码。主要是Tab核心类函数的定义、事件触发、新旧元素的高亮设置等。核心代码如下:
- var Tab = function (element) {
- this.element = $(element) // 指定当前元素
- }
- // 显示逻辑,触发show和shown事件
- Tab.prototype.show = function (){};
- // active样式的应用,面板的隐藏和显示,以及tab的高亮与反高亮都使用了该方法
- Tab.prototype.activate = function (){};
上面代码里的两个原型方法的作用分别如下:
❑show方法主要是触发show事件,然后调用activate原型方法,最后再触发shown事件。
❑activate用于取消原来元素的高亮设置,对现有元素进行高亮设置,然后触发动画和回调函数。
上述原型方法的详细源码如下:
- // 显示逻辑,触发show和shown事件
- Tab.prototype.show = function () {
- var $this = this.element // 当前tab
- var $ul = $this.closest('ul:not(.dropdown-menu)')
- // 找到最近的ul(但不是下拉菜单),其实就是父元素
- var selector = $this.data('target') // 找到当前tab上的data-target属性值
- if (!selector) { // 如果data-target属性不存在
- selector = $this.attr('href') // 再查找href值
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // IE7特殊处理
- }
- // 如果当前tab已经是活动状态了,即父元素li上有active样式的话,直接返回,不做任何处理
- if ($this.parent('li').hasClass('active')) return
- // 找到上一个元素,即上一个带有active样式的li里的a元
- var previous = $ul.find('.active:last a')[0]
- // 设置show事件,注意此时的relatedTarget是上一个元素(自定义回调时使用,见JavaScript用法里的示例)
- var e = $.Event('show.bs.tab', {
- relatedTarget: previous
- })
- $this.trigger(e) // 触发show事件
- if (e.isDefaultPrevented()) return // 如果自定义回调里阻止了默认行为,则不再继续处理
- var $target = $(selector) // 要激活显示的面板,即target(或href)里的值所对应的元素
- this.activate($this.parent('li'), $ul) // 高亮显示当前tab
- this.activate($target, $target.parent(), function () {
- // 显示对应的面板,并在回调里触发shown事件
- $this.trigger({
- type: 'shown.bs.tab',
- relatedTarget: previous
- })
- })
- }
- // active样式的应用,面板的隐藏和显示,以及tab的高亮与反高亮都使用了该方法
- Tab.prototype.activate = function (element, container, callback) {
- // 查找当前容器(nav里的ul或者tab-content)里所有有active样式的元素
- var $active = container.find('> .active')
- var transition = callback
- && $.support.transition
- && $active.hasClass('fade') // 判断是使用回调还是动画
- function next() {
- $active
- .removeClass('active') // 去除其他元素的active样式
- .find('> .dropdown-menu > .active')
- // 包括li元素里面的下拉菜单里的active样式也要去除
- .removeClass('active')
- element.addClass('active') // 给当前被单击的元素添加active高亮样式
- if (transition) {
- element[0].offsetWidth // 如果支持动画,就重绘页面
- element.addClass('in') // 并添加in样式,去除透明
- } else {
- element.removeClass('fade') // 否则删除fade
- }
- if (element.parent('.dropdown-menu')) {
- // 如果单击的是下拉菜单里的颜色,则查找父元素(即ul)
- // 将离下拉菜单最近的li.dropdown元素(一般都是其父元素)进行高亮
- element.closest('li.dropdown').addClass('active')
- }
- callback && callback() // 最后,如果有回调,就执行回调
- }
- transition ? // 如果支持动画
- $active
- .one($.support.transition.end, next) // 就在动画结束后,执行next
- .emulateTransitionEnd(150) :
- next() // 否则,直接执行next
- $active.removeClass('in')
- }
步骤3 jQuery插件定义。是在jQuery上定义$.fn. tab插件,和Modal插件一样,在实例化之前,需要收集option参数。源码如下:
- var old = $.fn.tab
- // 保留其他库的$.fn.tab代码(如果定义的话),以便在noConflict之后,可以继续使用该老代码
- $.fn.tab = function (option) {
- return this.each(function () { // 根据选择器,遍历所有符合规则的元素
- var $this = $(this)
- var data = $this.data('bs.tab') // 查询当前元素上是否已经有了tab实例
- if (!data) $this.data('bs.tab', (data = new Tab(this)))
- // 如果没有,就初始化一个,并传入this
- if (typeof option == 'string') data[option]()
- // 如果option是字符,说明传入的是方法,直接调用该方法
- })
- }
- $.fn.tab.Constructor = Tab // 重设插件构造器,可以通过该属性获取插件的真实类函数
步骤4 防冲突处理。源码如下:
- // 防冲突处理
- $.fn.tab.noConflict = function () {
- $.fn.tab = old // 恢复以前的旧代码
- return this // 将$.fn.tab.noConflict()设置为Bootstrap的tab插件
- }
步骤5 绑定触发事件。源码如下:
- // 绑定触发事件
- // 查询所有组合条件(带data-toggle="tab"或者data-toggle="pill"属性)的元素
- $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle=
- "pill"]', function (e) {
- e.preventDefault() // 阻止默认行为
- $(this).tab('show') // 在单击事件上触发tab组件的show方法
- })
如果要使用自己风格的data-toggle的话,就要自己写代码触发事件了。比如要使用一个自定义属性data-toggle="switch",那绑定代码就应该如下:
- $(document).on('click.bs.tab.data-api', '[data-toggle="switch"]', function (e) {
- e.preventDefault()
- $(this).tab('show')
- })
注意
从下一节开始,我们将忽略步骤4(防冲突)的叙述,因为所有的插件在这一步的代码都一样,只是换个名字而已。也就是说,以后的源码分析,将只分析步骤2(类和原型方法定义)、步骤3(fn插件制作)和步骤5(事件绑定)。