Bootstrap源码:tab.js

bootstrap的tab.js封装了tab组件的相关功能,tab组件在网页里面非常常见,可以有限的空间里展示更多的网页内容。由于tab组件比之前的alert组件和button组件提供的功能相对高级一些,与之对应的html结构也比前两个组件复杂一些。在了解tab.js相关源码前,有必要先了解tab组件的html结构:

<div>
    <ul class="nav nav-tabs" >
		<li class="">
			<a href="#home" data-toggle="tab" aria-expanded="false">Home</a>
		</li>
		<li class="">
			<a href="#profile" data-toggle="tab" aria-expanded="false">Profile</a>
		</li>
		<li class="dropdown active">
			<a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-expanded="false">Dropdown 
				<span class="caret"></span>
			</a>
                        <ul class="dropdown-menu">
                			<li class="active">
                				<a href="#dropdown1" data-toggle="tab" aria-expanded="true">@fat</a>
                			</li>
                			<li class="">
                				<a href="#dropdown2" data-toggle="tab" aria-expanded="false">@mdo</a>
                			</li>
                        </ul>
                </li>
    </ul>
    <div class="tab-content">
		<div class="tab-pane fade" id="home" >
			<p>1</p>
		</div>
		<div class="tab-pane fade" id="profile" >
			<p>2</p>
		</div>
		<div class="tab-pane fade active in" id="dropdown1">
			<p>3</p>
		</div>
		<div class="tab-pane fade" id="dropdown2">
			<p>4</p>
		</div>
    </div>
 </div>

最外面的div是整个tab组件的容器,不可缺少。tab组件分成两部分,第一部分是导航部分,使用nav类;第二部分是内容部分,使用tab-content类。切换tab组件的关键元素是导航部分里面带有data-toggle="toggle"的a元素,相关事件都是由这些a元素的点击事件触发的。当前激活的tab组件,导航部分和内容部分相应的元素都会带有active的css类。比如以上html中就能看到导航部分的直接li子元素有一个是有active类的,内容部分里面某一个带有tab-pane类的div元素同时也有active的类。需要注意的是,bs的tab组件同时还支持导航部分里面使用下拉菜单组件,这样当点击某个下拉菜单相关的li时,并不会直接显示相应的tab-pane元素,而是会弹出下拉菜单,当点击下拉菜单里面的下拉项时,tab组件的内容部分会显示相应的tab-pane元素。如图:

Bootstrap源码:tab.js

接下来看tab.js的代码。

1. 先看plugin definition(略,比较简单,基本同button.js)

2. data-api

var clickHandler = function (e) {
    e.preventDefault()
    Plugin.call($(this), 'show')
  }
  $(document)
    .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
    .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)

由这段代码可以看出两点:

一是引发tab组件切换的元素除了带有data-toggle="tab"属性的元素外,还有data-toggle="pill"的元素,这主要是因为bs的导航组件除了这种tab式的以外,还有胶囊式的,也成为nav-pills,如:

Bootstrap源码:tab.js

二是其实Plugin.call($(this), 'show')这种写法,还不如$(this).tab('show')来的直接。

3. 关键方法源码解析(分析内容直接写成注释了,这样更清晰,便于自己今后复习)

Tab.prototype.show = function () {
    var $this    = this.element
    var $ul      = $this.closest('ul:not(.dropdown-menu)')//这里必须加:not(.dropdown-menu),因为如果不加这个,在带有下拉菜单的tab组件中,点下拉菜单里面的data-toggle元素,获取到的ul元素不是tab组件导航部分的容器ul元素
    var selector = $this.data('target')//从这里可以看出,可以在data-toggle元素上,通过data-target属性来指定相应的tab-pane元素,不过该属性必须是有效的jquery选择器字符串
    if (!selector) {
      selector = $this.attr('href')
      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }//这个if的处理,是考虑那种没有配置data-target属性的时候,直接通过data-toggle元素的href属性值来获取相应的target选择器,从本文最顶部的html结构中就能看到相关的配置
    if ($this.parent('li').hasClass('active')) return//当前激活的tab组件,再次点击,直接返回,不再处理
    var $previous = $ul.find('.active:last a')
    var hideEvent = $.Event('hide.bs.tab', {
      relatedTarget: $this[0]
    })
    var showEvent = $.Event('show.bs.tab', {
      relatedTarget: $previous[0]
    })
    $previous.trigger(hideEvent)//对于上一个激活的元素来说,需要触发它的hide事件,并且把事件的relatedTarget设置成当前激活的元素
    $this.trigger(showEvent)//对于当前激活的元素来说,需要触发它的show事件,并且把事件的relatedTarget设置成上一个激活的元素
    //但是需要注意的是,bs的大部分组件都是带有动画过程的,所以大部分组件的显示隐藏都会由一对事件,如show和shown,体现出开始显示,显示动画和显示完成的一个过程
    //上面两行代码,都是在动画显示前触发的事件,在动画结束后还会再触发一次事件,回调动画完成之后的处理
    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
    var $target = $(selector)
    this.activate($this.closest('li'), $ul)
    this.activate($target, $target.parent(), function () {
      $previous.trigger({
        type: 'hidden.bs.tab',
        relatedTarget: $this[0]
      })
      $this.trigger({
        type: 'shown.bs.tab',
        relatedTarget: $previous[0]
      })
    })
    //activate方法调用了两次,分别用来完成导航和内容部分的显示隐藏处理,相关代码分析如下:
  }
Tab.prototype.activate = function (element, container, callback) {
    var $active    = container.find('> .active')//不管是导航部分还是内容部分,激活的元素都是带有active类的元素
    var transition = callback
      && $.support.transition
      && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
      //transition表示是否需要动画的条件
      //这个是这样的,在show方法里,导航部分相关的activate调用并没有传递callback参数,所以导航部分是肯定不会使用动画的
      //而内容部分是根据tab-pane元素是否具有fade属性来决定是否采用动画的
      
    function next() {
      $active
        .removeClass('active')
        .find('> .dropdown-menu > .active')
          .removeClass('active')
        .end()
        .find('[data-toggle="tab"]')
          .attr('aria-expanded', false)
      //以上做的就是把上次激活的元素取消激活,不过额外添加了一些针对下拉菜单的特殊处理,如果上次激活的是一个下拉菜单,还得下拉菜单里面激活的菜单项取消激活,所以才有了这个调用:    .find('> .dropdown-menu > .active').removeClass('active')
      element
        .addClass('active')
        .find('[data-toggle="tab"]')
          .attr('aria-expanded', true)
      //以上做的就是激活当前点击的元素或者相对应的tab-pane元素了   
      if (transition) {
        element[0].offsetWidth // reflow for transition,这句一直没弄明白啥意思,既不是复制,也不是函数调用,有可能纯粹是为了刷新dom的目的,因为offsetWidth是需要计算dom的
        element.addClass('in')//动画结束,添加in类,设置opactity=1
      } else {
        element.removeClass('fade')//不需要动画的,自然要fade类去掉,不然就变透明了
      }
      if (element.parent('.dropdown-menu').length) {
      //下面这段是用来处理带有下拉菜单场景的
      //当点击的是下拉菜单的菜单项时,element其实是下拉菜单dropdown-menu里面的li元素,而不是tab组件导航部分里面的li元素,所以要通过下面的代码给导航部分的元素加上active类
        element
          .closest('li.dropdown')
            .addClass('active')
          .end()
          .find('[data-toggle="tab"]')
            .attr('aria-expanded', true)
      }
      callback && callback()
    }
    
    //下面这段的分析见transition.js
    $active.length && transition ?
      $active
        .one('bsTransitionEnd', next)
        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
      next()
    $active.removeClass('in')//in这个css类肯定是跟fade类一起搭配使用的,因为fade类配置了opacity=0,而in类配置了opacity=1,在动画运行之前,肯定要先把opacity还原成0,然后动画结束再把opacity设置成1即可,这样才能看到明显的动画效果。
    //不过对于本来就没有fade属性的元素来说,这个代码就没有什么影响了
  }





你可能感兴趣的:(bootstrap,bootstrap源码)