You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
library_manage_system/WebContent/js/bootstrap.js

2600 lines
94 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under the MIT license
*/
// 检查是否加载了jQuery如果没有则抛出错误
if (typeof jQuery === 'undefined') {
throw new Error('Bootstrap\'s JavaScript requires jQuery')
}
+function ($) {
'use strict';
// 获取当前页面加载的jQuery版本
var version = $.fn.jquery.split(' ')[0].split('.')
// 如果jQuery版本不符合要求小于1.9.1或大于3则抛出错误
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
}
}(jQuery);
/* ========================================================================
* Bootstrap: transition.js v3.3.7
* http://getbootstrap.com/javascript/#transitions
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// 检查浏览器是否支持CSS过渡
// ============================================================
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd', // Safari浏览器
MozTransition : 'transitionend', // Firefox浏览器
OTransition : 'oTransitionEnd otransitionend', // Opera浏览器
transition : 'transitionend' // 标准浏览器
}
// 遍历不同的浏览器前缀,检查支持的过渡事件
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // 如果浏览器不支持过渡例如IE8返回false
}
// 模拟过渡结束事件
$.fn.emulateTransitionEnd = function (duration) {
var called = false
var $el = this
// 绑定过渡结束事件
$(this).one('bsTransitionEnd', function () { called = true })
var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
setTimeout(callback, duration) // 设置超时时间
return this
}
$(function () {
$.support.transition = transitionEnd()
// 如果不支持过渡,直接返回
if (!$.support.transition) return
// 定义过渡结束事件
$.event.special.bsTransitionEnd = {
bindType: $.support.transition.end,
delegateType: $.support.transition.end,
handle: function (e) {
if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
}
}
})
}(jQuery);
/* ========================================================================
* Bootstrap: alert.js v3.3.7
* http://getbootstrap.com/javascript/#alerts
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// 定义ALERT类
// ======================
var dismiss = '[data-dismiss="alert"]' // 关闭按钮的选择器
var Alert = function (el) {
$(el).on('click', dismiss, this.close) // 点击关闭按钮时调用close方法
}
Alert.VERSION = '3.3.7'
Alert.TRANSITION_DURATION = 150 // 过渡时间
// 关闭alert的实现
Alert.prototype.close = function (e) {
var $this = $(this)
var selector = $this.attr('data-target') // 获取目标元素
// 如果没有定义target尝试通过href获取目标
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // 为兼容IE7做的处理
}
var $parent = $(selector === '#' ? [] : selector)
if (e) e.preventDefault() // 防止默认事件
if (!$parent.length) {
$parent = $this.closest('.alert') // 如果没有找到目标元素,查找最近的.alert元素
}
// 触发关闭事件
$parent.trigger(e = $.Event('close.bs.alert'))
if (e.isDefaultPrevented()) return // 如果关闭事件被取消,则返回
$parent.removeClass('in') // 移除'in'类,开始过渡效果
function removeElement() {
$parent.detach().trigger('closed.bs.alert').remove() // 从DOM中移除该alert元素
}
// 如果支持过渡效果,则使用过渡动画移除元素,否则直接移除
$.support.transition && $parent.hasClass('fade') ?
$parent
.one('bsTransitionEnd', removeElement)
.emulateTransitionEnd(Alert.TRANSITION_DURATION) :
removeElement()
}
// 定义Alert插件
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.alert')
if (!data) $this.data('bs.alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this) // 调用Alert的方法
})
}
var old = $.fn.alert
$.fn.alert = Plugin
$.fn.alert.Constructor = Alert
// ALERT NO CONFLICT
// =================
$.fn.alert.noConflict = function () {
$.fn.alert = old
return this
}
// ALERT DATA-API
// ==============
$(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) // 绑定关闭事件
}(jQuery);
/* ========================================================================
* Bootstrap: button.js v3.3.7
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// BUTTON公共类定义
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false // 按钮的加载状态
}
Button.VERSION = '3.3.7'
Button.DEFAULTS = {
loadingText: 'loading...' // 加载中的文字
}
// 设置按钮的状态
Button.prototype.setState = function (state) {
var d = 'disabled' // 禁用状态
var $el = this.$element
var val = $el.is('input') ? 'val' : 'html' // 判断按钮类型
var data = $el.data()
state += 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]()) // 记录初始文本
// 延时更新按钮文本,并根据状态启用或禁用按钮
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d).prop(d, true)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d).prop(d, false)
}
}, this), 0)
}
// 切换按钮的状态(激活/取消激活)
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked')) changed = false
$parent.find('.active').removeClass('active')
this.$element.addClass('active')
} else if ($input.prop('type') == 'checkbox') {
if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
this.$element.toggleClass('active')
}
$input.prop('checked', this.$element.hasClass('active'))
if (changed) $input.trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
this.$element.toggleClass('active')
}
}
// BUTTON插件定义
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.button')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle() // 切换按钮状态
else if (option) data.setState(option) // 设置按钮状态
})
}
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// BUTTON无冲突模式
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
}
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target).closest('.btn')
Plugin.call($btn, 'toggle')
if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
// 防止单选框和复选框的双击和多选(取消选择)
e.preventDefault()
// 让目标组件接收到焦点
if ($btn.is('input,button')) $btn.trigger('focus')
else $btn.find('input:visible,button:visible').first().trigger('focus')
}
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) // 添加焦点样式
})
}(jQuery);
/* ========================================================================
* Bootstrap: carousel.js v3.3.7
* http://getbootstrap.com/javascript/#carousel
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// CAROUSEL 类定义
// =========================
var Carousel = function (element, options) {
this.$element = $(element) // 获取carousel元素
this.$indicators = this.$element.find('.carousel-indicators') // 获取carousel指示器例如小圆点
this.options = options // 存储配置选项
this.paused = null // 暂停状态
this.sliding = null // 滑动状态
this.interval = null // 自动切换间隔
this.$active = null // 当前活动项
this.$items = null // 所有的carousel项
// 如果启用了键盘控制,监听按键事件
this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
// 如果暂停选项为'hover',并且当前设备不支持触摸事件,设置鼠标悬停事件来暂停/恢复轮播
this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
.on('mouseenter.bs.carousel', $.proxy(this.pause, this)) // 鼠标进入时暂停
.on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) // 鼠标离开时恢复轮播
}
Carousel.VERSION = '3.3.7' // 版本信息
Carousel.TRANSITION_DURATION = 600 // 过渡持续时间(毫秒)
Carousel.DEFAULTS = {
interval: 5000, // 默认切换时间间隔(毫秒)
pause: 'hover', // 鼠标悬停时暂停
wrap: true, // 是否循环轮播
keyboard: true // 是否启用键盘控制
}
// 键盘事件处理
Carousel.prototype.keydown = function (e) {
if (/input|textarea/i.test(e.target.tagName)) return // 如果焦点在输入框或文本区域上,不处理
switch (e.which) {
case 37: this.prev(); break // 左箭头
case 39: this.next(); break // 右箭头
default: return
}
e.preventDefault() // 阻止默认行为
}
// 开始自动轮播
Carousel.prototype.cycle = function (e) {
e || (this.paused = false) // 如果没有传入事件,恢复未暂停状态
// 清除现有的interval
this.interval && clearInterval(this.interval)
// 如果设置了自动切换并且没有暂停,重新启动自动轮播
this.options.interval
&& !this.paused
&& (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
return this
}
// 获取当前项在所有项中的索引
Carousel.prototype.getItemIndex = function (item) {
this.$items = item.parent().children('.item') // 获取所有项
return this.$items.index(item || this.$active) // 返回索引
}
// 获取用于切换方向的下一项
Carousel.prototype.getItemForDirection = function (direction, active) {
var activeIndex = this.getItemIndex(active) // 获取当前活动项的索引
var willWrap = (direction == 'prev' && activeIndex === 0) // 是否回绕到最后一项
|| (direction == 'next' && activeIndex == (this.$items.length - 1)) // 是否回绕到第一项
if (willWrap && !this.options.wrap) return active // 如果不循环,返回当前项
var delta = direction == 'prev' ? -1 : 1 // 根据方向计算索引增量
var itemIndex = (activeIndex + delta) % this.$items.length // 计算下一项的索引
return this.$items.eq(itemIndex) // 返回下一项
}
// 跳转到指定位置
Carousel.prototype.to = function (pos) {
var that = this
var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) // 获取当前活动项的索引
if (pos > (this.$items.length - 1) || pos < 0) return // 如果位置超出范围,返回
if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // 如果正在滑动,等待滑动完成后再跳转
if (activeIndex == pos) return this.pause().cycle() // 如果目标位置是当前项,暂停并重新开始自动轮播
return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) // 根据位置切换方向
}
// 暂停轮播
Carousel.prototype.pause = function (e) {
e || (this.paused = true) // 如果没有传入事件,设置为暂停状态
// 如果有过渡效果,触发过渡结束
if (this.$element.find('.next, .prev').length && $.support.transition) {
this.$element.trigger($.support.transition.end)
this.cycle(true) // 暂停后重新开始
}
this.interval = clearInterval(this.interval) // 清除间隔
return this
}
// 切换到下一项
Carousel.prototype.next = function () {
if (this.sliding) return // 如果正在滑动,返回
return this.slide('next') // 切换到下一项
}
// 切换到上一项
Carousel.prototype.prev = function () {
if (this.sliding) return // 如果正在滑动,返回
return this.slide('prev') // 切换到上一项
}
// 执行滑动操作
Carousel.prototype.slide = function (type, next) {
var $active = this.$element.find('.item.active') // 当前活动项
var $next = next || this.getItemForDirection(type, $active) // 获取下一项
var isCycling = this.interval // 是否正在自动轮播
var direction = type == 'next' ? 'left' : 'right' // 根据方向设置过渡方向
var that = this
if ($next.hasClass('active')) return (this.sliding = false) // 如果下一项已经是活动项,返回
var relatedTarget = $next[0]
var slideEvent = $.Event('slide.bs.carousel', {
relatedTarget: relatedTarget,
direction: direction
})
this.$element.trigger(slideEvent) // 触发slide事件
if (slideEvent.isDefaultPrevented()) return // 如果事件被取消,返回
this.sliding = true // 设置为滑动状态
isCycling && this.pause() // 如果正在轮播,先暂停
// 更新指示器状态
if (this.$indicators.length) {
this.$indicators.find('.active').removeClass('active')
var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) // 获取下一项的指示器
$nextIndicator && $nextIndicator.addClass('active')
}
var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // 滑动完成事件
if ($.support.transition && this.$element.hasClass('slide')) {
$next.addClass(type) // 给下一项添加过渡类型
$next[0].offsetWidth // 强制重排
$active.addClass(direction) // 给当前项添加方向类
$next.addClass(direction) // 给下一项添加方向类
$active
.one('bsTransitionEnd', function () {
$next.removeClass([type, direction].join(' ')).addClass('active') // 移除过渡效果,设置为活动项
$active.removeClass(['active', direction].join(' ')) // 移除活动状态
that.sliding = false // 重置滑动状态
setTimeout(function () {
that.$element.trigger(slidEvent) // 触发滑动完成事件
}, 0)
})
.emulateTransitionEnd(Carousel.TRANSITION_DURATION) // 模拟过渡结束
} else {
$active.removeClass('active') // 直接切换,不使用过渡效果
$next.addClass('active')
this.sliding = false
this.$element.trigger(slidEvent) // 触发滑动完成事件
}
isCycling && this.cycle() // 如果正在轮播,重新开始
return this
}
// CAROUSEL 插件定义
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.carousel') // 获取现有实例
var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) // 合并配置
var action = typeof option == 'string' ? option : options.slide // 获取操作类型
if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) // 如果没有实例,创建一个新的实例
if (typeof option == 'number') data.to(option) // 如果是数字,跳转到指定位置
else if (action) data[action]() // 如果是字符串,执行相应操作
else if (options.interval) data.pause().cycle() // 如果有interval暂停并启动自动轮播
})
}
var old = $.fn.carousel // 保存之前的插件定义
$.fn.carousel = Plugin // 替换插件方法
$.fn.carousel.Constructor = Carousel // 设置构造函数
// CAROUSEL 不冲突模式
// ====================
$.fn.carousel.noConflict = function () {
$.fn.carousel = old // 恢复原来的插件定义
return this
}
// CAROUSEL 数据API
// =================
var clickHandler = function (e) {
var href
var $this = $(this)
var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // 获取目标元素
if (!$target.hasClass('carousel')) return // 如果目标不是carousel返回
var options = $.extend({}, $target.data(), $this.data()) // 合并配置选项
var slideIndex = $this.attr('data-slide-to') // 获取数据属性中的slide索引
if (slideIndex) options.interval = false // 禁用自动切换
Plugin.call($target, options) // 调用插件
if (slideIndex) {
$target.data('bs.carousel').to(slideIndex) // 跳转到指定位置
}
e.preventDefault() // 阻止默认行为
}
$(document)
.on('click.bs.carousel.data-api', '[data-slide]', clickHandler) // 监听点击事件
.on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) // 监听点击事件
$(window).on('load', function () {
$('[data-ride="carousel"]').each(function () {
var $carousel = $(this)
Plugin.call($carousel, $carousel.data()) // 初始化所有的carousel元素
})
})
}(jQuery);
/* ========================================================================
* Bootstrap: collapse.js v3.3.7
* http://getbootstrap.com/javascript/#collapse
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+function ($) {
'use strict';
// COLLAPSE 公共类定义
// ================================
var Collapse = function (element, options) {
this.$element = $(element) // 获取目标元素
this.options = $.extend({}, Collapse.DEFAULTS, options) // 合并默认配置和传入的配置
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + // 获取触发 collapse 的元素
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null // 初始化过渡状态
if (this.options.parent) {
this.$parent = this.getParent() // 如果设置了 parent获取父元素
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger) // 向元素和触发器添加 aria 属性和 collapsed 类
}
if (this.options.toggle) this.toggle() // 如果 toggle 为 true则自动调用 toggle 方法
}
Collapse.VERSION = '3.3.7' // 版本信息
Collapse.TRANSITION_DURATION = 350 // 过渡持续时间(毫秒)
Collapse.DEFAULTS = {
toggle: true // 默认启用 toggle
}
// 获取维度(宽度或高度)
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width') // 判断是否为宽度
return hasWidth ? 'width' : 'height' // 返回宽度或高度
}
// 展开方法
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return // 如果正在过渡或已经展开,返回
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') // 获取所有正在展开或过渡中的元素
if (actives && actives.length) {
activesData = actives.data('bs.collapse') // 获取活动元素的 collapse 数据
if (activesData && activesData.transitioning) return // 如果活动元素正在过渡,返回
}
var startEvent = $.Event('show.bs.collapse') // 触发 show 事件
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return // 如果事件被取消,返回
// 如果有其他活动项,先将它们隐藏
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension() // 获取维度(宽度或高度)
this.$element
.removeClass('collapse') // 移除 collapse 类
.addClass('collapsing') // 添加 collapsing 类
// 设置初始高度或宽度为 0
.attr('aria-expanded', true) // 设置 aria-expanded 属性为 true
this.$trigger
.removeClass('collapsed') // 移除 collapsed 类
.attr('aria-expanded', true) // 设置 aria-expanded 属性为 true
this.transitioning = 1 // 设置过渡状态为 1
var complete = function () {
this.$element
.removeClass('collapsing') // 移除 collapsing 类
.addClass('collapse in') // 添加 collapse 和 in 类
[dimension]('') // 恢复元素的原始维度
this.transitioning = 0 // 重置过渡状态
this.$element
.trigger('shown.bs.collapse') // 触发 shown 事件
}
if (!$.support.transition) return complete.call(this) // 如果不支持过渡,直接调用 complete
var scrollSize = $.camelCase(['scroll', dimension].join('-')) // 获取滚动大小
this.$element
.one('bsTransitionEnd', $.proxy(complete, this)) // 在过渡结束时触发 complete
.emulateTransitionEnd(Collapse.TRANSITION_DURATION) // 模拟过渡结束
[dimension](this.$element[0][scrollSize]) // 设置滚动尺寸
}
// 收起方法
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return // 如果正在过渡或没有展开,返回
var startEvent = $.Event('hide.bs.collapse') // 触发 hide 事件
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return // 如果事件被取消,返回
var dimension = this.dimension() // 获取维度(宽度或高度)
this.$element[dimension](this.$element[dimension]())[0].offsetHeight // 强制重排
this.$element
.addClass('collapsing') // 添加 collapsing 类
.removeClass('collapse in') // 移除 collapse 和 in 类
.attr('aria-expanded', false) // 设置 aria-expanded 属性为 false
this.$trigger
.addClass('collapsed') // 添加 collapsed 类
.attr('aria-expanded', false) // 设置 aria-expanded 属性为 false
this.transitioning = 1 // 设置过渡状态为 1
var complete = function () {
this.transitioning = 0 // 重置过渡状态
this.$element
.removeClass('collapsing') // 移除 collapsing 类
.addClass('collapse') // 添加 collapse 类
.trigger('hidden.bs.collapse') // 触发 hidden 事件
}
if (!$.support.transition) return complete.call(this) // 如果不支持过渡,直接调用 complete
this.$element
// 设置维度为 0
.one('bsTransitionEnd', $.proxy(complete, this)) // 在过渡结束时触发 complete
.emulateTransitionEnd(Collapse.TRANSITION_DURATION) // 模拟过渡结束
}
// 切换展开和收起状态
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']() // 如果已经展开则收起,否则展开
}
// 获取父元素(用于手风琴效果)
Collapse.prototype.getParent = function () {
return $(this.options.parent) // 获取父元素
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') // 查找子元素
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) // 为目标元素添加 aria 和 collapsed 类
}, this))
.end()
}
// 向元素和触发器添加 aria 属性和 collapsed 类
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in') // 判断元素是否展开
$element.attr('aria-expanded', isOpen) // 设置 aria-expanded 属性
$trigger
.toggleClass('collapsed', !isOpen) // 如果元素未展开,添加 collapsed 类
.attr('aria-expanded', isOpen) // 设置 aria-expanded 属性
}
// 获取触发器对应的目标元素
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target') // 获取 data-target 属性
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // 如果没有 data-target使用 href
return $(target) // 返回目标元素
}
// COLLAPSE 插件定义
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse') // 获取现有的 collapse 实例
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) // 合并默认配置和传入的配置
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false // 如果没有实例且 option 是 'show' 或 'hide',则禁用 toggle
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) // 如果没有实例,创建新实例
if (typeof option == 'string') data[option]() // 如果 option 是字符串,调用对应的方法
})
}
var old = $.fn.collapse // 保存原始的 collapse 方法
$.fn.collapse = Plugin // 替换为新的 Plugin 方法
$.fn.collapse.Constructor = Collapse // 设置构造函数为 Collapse
// COLLAPSE 不冲突模式
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old // 恢复原来的 collapse 方法
return this
}
// COLLAPSE 数据API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault() // 如果没有 data-target阻止默认行为
var $target = getTargetFromTrigger($this) // 获取目标元素
var data = $target.data('bs.collapse') // 获取 collapse 数据
var option = data ? 'toggle' : $this.data() // 如果已有实例,则执行 toggle否则使用传入的配置
Plugin.call($target, option) // 调用 Plugin
})
}(jQuery);
/* ========================================================================
* Bootstrap: dropdown.js v3.3.7
* http://getbootstrap.com/javascript/#dropdowns
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// DROPDOWN 类定义
// =========================
var backdrop = '.dropdown-backdrop' // 定义背景遮罩类名
var toggle = '[data-toggle="dropdown"]' // 定义触发 dropdown 的元素选择器
var Dropdown = function (element) {
$(element).on('click.bs.dropdown', this.toggle) // 为点击事件绑定 toggle 方法
}
Dropdown.VERSION = '3.3.7' // 版本信息
// 获取父元素
function getParent($this) {
var selector = $this.attr('data-target') // 获取 data-target 属性
if (!selector) {
selector = $this.attr('href') // 如果没有 data-target使用 href 属性
selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // 对 IE7 进行兼容处理
}
var $parent = selector && $(selector) // 获取目标元素
return $parent && $parent.length ? $parent : $this.parent() // 返回父元素,如果没有找到则返回当前元素的父级
}
// 清除菜单(关闭所有打开的下拉菜单)
function clearMenus(e) {
if (e && e.which === 3) return // 如果点击的是右键,则不关闭菜单
$(backdrop).remove() // 移除背景遮罩
$(toggle).each(function () {
var $this = $(this)
var $parent = getParent($this) // 获取父元素
var relatedTarget = { relatedTarget: this }
if (!$parent.hasClass('open')) return // 如果菜单没有展开,返回
if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return // 点击在输入框或文本区内,则不关闭
$parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) // 触发隐藏事件
if (e.isDefaultPrevented()) return // 如果默认行为被阻止,则返回
$this.attr('aria-expanded', 'false') // 更新 aria-expanded 属性
$parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) // 移除 open 类,并触发 hidden 事件
})
}
// 切换下拉菜单的显示状态
Dropdown.prototype.toggle = function (e) {
var $this = $(this)
if ($this.is('.disabled, :disabled')) return // 如果元素被禁用,返回
var $parent = getParent($this) // 获取父元素
var isActive = $parent.hasClass('open') // 判断当前菜单是否处于打开状态
clearMenus() // 关闭其他打开的菜单
if (!isActive) {
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
// 如果是移动端设备,则使用背景遮罩,因为点击事件无法委托
$(document.createElement('div'))
.addClass('dropdown-backdrop') // 创建背景遮罩
.insertAfter($(this)) // 插入到当前元素后面
.on('click', clearMenus) // 点击遮罩时关闭菜单
}
var relatedTarget = { relatedTarget: this }
$parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) // 触发显示事件
if (e.isDefaultPrevented()) return // 如果默认行为被阻止,返回
$this
.trigger('focus') // 触发 focus 事件
.attr('aria-expanded', 'true') // 设置 aria-expanded 属性为 true
$parent
.toggleClass('open') // 切换 open 类,控制下拉菜单的显示与隐藏
.trigger($.Event('shown.bs.dropdown', relatedTarget)) // 触发 shown 事件
}
return false // 阻止事件冒泡
}
// 处理键盘事件上、下、Esc、空格
Dropdown.prototype.keydown = function (e) {
if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return // 只处理上、下箭头、Esc 和空格键,且不在输入框或文本区内处理
var $this = $(this)
e.preventDefault() // 阻止默认事件
e.stopPropagation() // 阻止事件冒泡
if ($this.is('.disabled, :disabled')) return // 如果元素被禁用,返回
var $parent = getParent($this) // 获取父元素
var isActive = $parent.hasClass('open') // 判断当前菜单是否处于打开状态
if (!isActive && e.which != 27 || isActive && e.which == 27) {
if (e.which == 27) $parent.find(toggle).trigger('focus') // 如果按下 Esc 键,聚焦触发器
return $this.trigger('click') // 触发 click 事件
}
var desc = ' li:not(.disabled):visible a' // 查找可见且不被禁用的菜单项
var $items = $parent.find('.dropdown-menu' + desc) // 获取所有有效的菜单项
if (!$items.length) return // 如果没有菜单项,返回
var index = $items.index(e.target) // 获取当前选中的菜单项的索引
if (e.which == 38 && index > 0) index-- // 上箭头,索引减一
if (e.which == 40 && index < $items.length - 1) index++ // 下箭头,索引加一
if (!~index) index = 0 // 如果索引无效,则从第一个开始
$items.eq(index).trigger('focus') // 聚焦到选中的菜单项
}
// DROPDOWN 插件定义
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.dropdown') // 获取现有的 dropdown 实例
if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) // 如果没有实例,创建一个新的实例
if (typeof option == 'string') data[option].call($this) // 如果传入的参数是字符串,则调用对应的方法
})
}
var old = $.fn.dropdown // 保存原始的 dropdown 方法
$.fn.dropdown = Plugin // 替换为新的 Plugin 方法
$.fn.dropdown.Constructor = Dropdown // 设置构造函数为 Dropdown
// DROPDOWN 不冲突模式
// ====================
$.fn.dropdown.noConflict = function () {
$.fn.dropdown = old // 恢复原来的 dropdown 方法
return this
}
// 应用到标准的 DROPDOWN 元素
// ===================================
$(document)
.on('click.bs.dropdown.data-api', clearMenus) // 点击时清除菜单
.on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) // 阻止 form 元素点击时冒泡
.on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) // 点击触发下拉菜单的切换
.on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) // 键盘事件处理
.on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) // 键盘事件处理(在下拉菜单内)
}(jQuery);
/* ========================================================================
* Bootstrap: modal.js v3.3.7
* http://getbootstrap.com/javascript/#modals
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// MODAL 类定义
// ======================
var Modal = function (element, options) {
this.options = options // 配置项
this.$body = $(document.body) // 获取文档的 body 元素
this.$element = $(element) // 当前 modal 元素
this.$dialog = this.$element.find('.modal-dialog') // 获取 modal 对话框元素
this.$backdrop = null // 背景遮罩
this.isShown = null // 模态框是否已显示
this.originalBodyPad = null // 原始 body padding
this.scrollbarWidth = 0 // 滚动条宽度
this.ignoreBackdropClick = false // 是否忽略背景点击事件
// 如果有 remote 配置项,加载远程内容
if (this.options.remote) {
this.$element
.find('.modal-content')
.load(this.options.remote, $.proxy(function () {
this.$element.trigger('loaded.bs.modal') // 内容加载完成后触发事件
}, this))
}
}
Modal.VERSION = '3.3.7' // 版本号
// 定义模态框过渡时间
Modal.TRANSITION_DURATION = 300
Modal.BACKDROP_TRANSITION_DURATION = 150
// 默认配置项
Modal.DEFAULTS = {
backdrop: true, // 背景是否显示
keyboard: true, // 是否响应键盘事件
show: true // 是否显示模态框
}
// 切换显示或隐藏模态框
Modal.prototype.toggle = function (_relatedTarget) {
return this.isShown ? this.hide() : this.show(_relatedTarget) // 如果模态框已显示,隐藏,否则显示
}
// 显示模态框
Modal.prototype.show = function (_relatedTarget) {
var that = this
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) // 触发 show 事件
this.$element.trigger(e) // 触发模态框的 show 事件
if (this.isShown || e.isDefaultPrevented()) return // 如果已显示或事件被阻止,返回
this.isShown = true // 标记为已显示
this.checkScrollbar() // 检查是否有滚动条
this.setScrollbar() // 设置滚动条
this.$body.addClass('modal-open') // 在 body 上添加类 modal-open
this.escape() // 响应键盘 Escape 键
this.resize() // 响应窗口大小变化
// 绑定点击事件,点击关闭按钮时隐藏模态框
this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
// 处理模态框对话框的鼠标事件
this.$dialog.on('mousedown.dismiss.bs.modal', function () {
that.$element.one('mouseup.dismiss.bs.modal', function (e) {
if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true // 如果点击了模态框,忽略背景点击
})
})
// 处理背景遮罩
this.backdrop(function () {
var transition = $.support.transition && that.$element.hasClass('fade') // 判断是否支持过渡动画
if (!that.$element.parent().length) {
that.$element.appendTo(that.$body) // 不移动模态框的 DOM 位置
}
that.$element
.show() // 显示模态框
.scrollTop(0) // 滚动到顶部
that.adjustDialog() // 调整模态框的位置
// 如果需要过渡动画
if (transition) {
that.$element[0].offsetWidth // 强制重新计算 DOM 样式
}
that.$element.addClass('in') // 添加显示类 in
that.enforceFocus() // 强制模态框聚焦
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) // 触发 shown 事件
transition ?
that.$dialog // 等待模态框过渡完成
.one('bsTransitionEnd', function () {
that.$element.trigger('focus').trigger(e)
})
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
that.$element.trigger('focus').trigger(e)
})
}
// 隐藏模态框
Modal.prototype.hide = function (e) {
if (e) e.preventDefault() // 阻止默认事件
e = $.Event('hide.bs.modal') // 触发 hide 事件
this.$element.trigger(e) // 触发模态框的 hide 事件
if (!this.isShown || e.isDefaultPrevented()) return // 如果未显示或事件被阻止,返回
this.isShown = false // 标记为未显示
this.escape() // 取消响应 Escape 键
this.resize() // 取消响应窗口大小变化
$(document).off('focusin.bs.modal') // 解除焦点事件监听
this.$element
.removeClass('in') // 移除显示类 in
.off('click.dismiss.bs.modal')
.off('mouseup.dismiss.bs.modal')
this.$dialog.off('mousedown.dismiss.bs.modal')
// 如果需要过渡动画
$.support.transition && this.$element.hasClass('fade') ?
this.$element
.one('bsTransitionEnd', $.proxy(this.hideModal, this))
.emulateTransitionEnd(Modal.TRANSITION_DURATION) :
this.hideModal() // 隐藏模态框
}
// 强制模态框聚焦
Modal.prototype.enforceFocus = function () {
$(document)
.off('focusin.bs.modal') // 防止无限循环聚焦
.on('focusin.bs.modal', $.proxy(function (e) {
if (document !== e.target &&
this.$element[0] !== e.target &&
!this.$element.has(e.target).length) {
this.$element.trigger('focus') // 如果点击的不是模态框内部元素,强制聚焦到模态框
}
}, this))
}
// 响应键盘 Escape 键
Modal.prototype.escape = function () {
if (this.isShown && this.options.keyboard) {
this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
e.which == 27 && this.hide() // 如果按下 Escape 键,隐藏模态框
}, this))
} else if (!this.isShown) {
this.$element.off('keydown.dismiss.bs.modal') // 移除键盘事件监听
}
}
// 响应窗口大小变化
Modal.prototype.resize = function () {
if (this.isShown) {
$(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
} else {
$(window).off('resize.bs.modal') // 移除窗口大小变化事件监听
}
}
// 隐藏模态框并移除背景
Modal.prototype.hideModal = function () {
var that = this
this.$element.hide()
this.backdrop(function () {
that.$body.removeClass('modal-open') // 移除 modal-open 类
that.resetAdjustments() // 重置样式
that.resetScrollbar() // 重置滚动条
that.$element.trigger('hidden.bs.modal') // 触发 hidden 事件
})
}
// 移除背景遮罩
Modal.prototype.removeBackdrop = function () {
this.$backdrop && this.$backdrop.remove()
this.$backdrop = null
}
// 创建背景遮罩
Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : '' // 判断是否需要过渡效果
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate // 判断是否支持过渡动画
this.$backdrop = $(document.createElement('div'))
.addClass('modal-backdrop ' + animate)
.appendTo(this.$body)
this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
if (this.ignoreBackdropClick) {
this.ignoreBackdropClick = false
return
}
if (e.target !== e.currentTarget) return
this.options.backdrop == 'static'
? this.$element[0].focus() // 如果配置为 static聚焦到模态框
: this.hide() // 否则隐藏模态框
}, this))
if (doAnimate) this.$backdrop[0].offsetWidth // 强制重新计算样式
this.$backdrop.addClass('in') // 显示背景
if (!callback) return
doAnimate ?
this.$backdrop
.one('bsTransitionEnd', callback)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in') // 隐藏背景
var callbackRemove = function () {
that.removeBackdrop()
callback && callback()
}
$.support.transition && this.$element.hasClass('fade') ?
this.$backdrop
.one('bsTransitionEnd', callbackRemove)
.emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
callbackRemove()
} else if (callback) {
callback()
}
}
// 以下方法用于处理溢出的模态框
Modal.prototype.handleUpdate = function () {
this.adjustDialog() // 调整模态框位置
}
// 调整模态框的位置
Modal.prototype.adjustDialog = function () {
var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight // 判断模态框是否溢出
this.$element.css({
paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', // 如果有滚动条,调整 padding
paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
})
}
// 重置模态框样式
Modal.prototype.resetAdjustments = function () {
this.$element.css({
paddingLeft: '',
paddingRight: ''
})
}
// 检查是否有滚动条
Modal.prototype.checkScrollbar = function () {
var fullWindowWidth = window.innerWidth
if (!fullWindowWidth) { // IE8 的兼容性处理
var documentElementRect = document.documentElement.getBoundingClientRect()
fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
}
this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth // 判断 body 是否有溢出
this.scrollbarWidth = this.measureScrollbar() // 获取滚动条宽度
}
// 设置滚动条样式
Modal.prototype.setScrollbar = function () {
var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
this.originalBodyPad = document.body.style.paddingRight || ''
if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) // 如果有滚动条,设置 padding
}
// 重置滚动条样式
Modal.prototype.resetScrollbar = function () {
this.$body.css('padding-right', this.originalBodyPad)
}
// 测量滚动条宽度
Modal.prototype.measureScrollbar = function () { // thx walsh
var scrollDiv = document.createElement('div')
scrollDiv.className = 'modal-scrollbar-measure'
this.$body.append(scrollDiv)
var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth // 获取滚动条宽度
this.$body[0].removeChild(scrollDiv)
return scrollbarWidth
}
// MODAL 插件定义
// =======================
function Plugin(option, _relatedTarget) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.modal')
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('bs.modal', (data = new Modal(this, options))) // 创建 Modal 实例
if (typeof option == 'string') data[option](_relatedTarget) // 如果传入方法名,执行对应方法
else if (options.show) data.show(_relatedTarget) // 默认显示模态框
})
}
var old = $.fn.modal // 保存旧的插件定义
$.fn.modal = Plugin // 定义 jQuery 插件
$.fn.modal.Constructor = Modal // 导出构造函数
// MODAL 无冲突处理
// =================
$.fn.modal.noConflict = function () {
$.fn.modal = old // 恢复旧的插件定义
return this
}
// MODAL 数据 API
// ==============
$(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
var $this = $(this)
var href = $this.attr('href')
var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // 获取目标元素
var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) // 获取选项
if ($this.is('a')) e.preventDefault() // 阻止默认行为
$target.one('show.bs.modal', function (showEvent) {
if (showEvent.isDefaultPrevented()) return // 如果模态框不会显示,取消焦点恢复操作
$target.one('hidden.bs.modal', function () {
$this.is(':visible') && $this.trigger('focus') // 隐藏时恢复焦点
})
})
Plugin.call($target, option, this) // 调用插件
})
}(jQuery);
/* ========================================================================
* Bootstrap: tooltip.js v3.3.7
* http://getbootstrap.com/javascript/#tooltip
* Inspired by the original jQuery.tipsy by Jason Frame
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TOOLTIP 公共类定义
// ===============================
var Tooltip = function (element, options) {
this.type = null; // Tooltip 类型
this.options = null; // Tooltip 配置
this.enabled = null; // Tooltip 是否启用
this.timeout = null; // 延时变量
this.hoverState = null; // 鼠标悬停状态
this.$element = null; // 绑定的 DOM 元素
this.inState = null; // 状态对象 {click, hover, focus}
this.init('tooltip', element, options); // 初始化 Tooltip 实例
}
Tooltip.VERSION = '3.3.7'; // Tooltip 版本
Tooltip.TRANSITION_DURATION = 150; // 动画过渡时间
// 默认配置
Tooltip.DEFAULTS = {
animation: true, // 是否启用动画
placement: 'top', // 提示框的位置(默认为顶部)
selector: false, // 是否启用选择器
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', // 提示框的 HTML 模板
trigger: 'hover focus', // 触发事件(默认是 hover 和 focus
title: '', // 提示框标题
delay: 0, // 延时显示/隐藏
html: false, // 是否支持 HTML 内容
container: false, // 提示框容器
viewport: {
selector: 'body', // 视口选择器
padding: 0 // 视口内边距
}
}
// 初始化方法
Tooltip.prototype.init = function (type, element, options) {
this.enabled = true; // 启用 Tooltip
this.type = type; // Tooltip 类型
this.$element = $(element); // 绑定元素
this.options = this.getOptions(options); // 获取配置
this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)); // 视口
this.inState = { click: false, hover: false, focus: false }; // 初始化状态
// 如果元素是文档对象且没有选择器,抛出错误
if (this.$element[0] instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!');
}
var triggers = this.options.trigger.split(' '); // 获取触发事件
// 为每个触发事件绑定相应的事件处理函数
for (var i = triggers.length; i--;) {
var trigger = triggers[i];
if (trigger == 'click') {
this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)); // 点击事件
} else if (trigger != 'manual') {
var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'; // 鼠标进入或获取焦点事件
var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'; // 鼠标离开或失去焦点事件
this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)); // 鼠标进入或获取焦点
this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)); // 鼠标离开或失去焦点
}
}
this.options.selector ? // 如果存在选择器,更新配置
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle(); // 修复标题
}
// 返回默认配置
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS;
}
// 获取合并后的配置
Tooltip.prototype.getOptions = function (options) {
options = $.extend({}, this.getDefaults(), this.$element.data(), options); // 合并配置
// 如果延时是数字类型,则转换为对象形式
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay,
hide: options.delay
};
}
return options;
}
// 获取代理选项
Tooltip.prototype.getDelegateOptions = function () {
var options = {};
var defaults = this.getDefaults();
// 合并不同于默认值的配置
this._options && $.each(this._options, function (key, value) {
if (defaults[key] != value) options[key] = value;
});
return options;
}
// 鼠标进入事件
Tooltip.prototype.enter = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type); // 获取当前实例
// 如果实例不存在,则创建一个新的实例
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
$(obj.currentTarget).data('bs.' + this.type, self); // 保存实例
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true; // 更新悬停或焦点状态
}
// 如果 Tooltip 已经显示或当前为悬停状态,则直接返回
if (self.tip().hasClass('in') || self.hoverState == 'in') {
self.hoverState = 'in';
return;
}
clearTimeout(self.timeout); // 清除超时
self.hoverState = 'in'; // 设置悬停状态
// 如果没有延时,立即显示;否则设置延时显示
if (!self.options.delay || !self.options.delay.show) return self.show();
self.timeout = setTimeout(function () {
if (self.hoverState == 'in') self.show(); // 延时显示
}, self.options.delay.show);
}
// 判断是否有激活状态
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true; // 如果有激活状态,返回 true
}
return false; // 否则返回 false
}
// 鼠标离开事件
Tooltip.prototype.leave = function (obj) {
var self = obj instanceof this.constructor ?
obj : $(obj.currentTarget).data('bs.' + this.type); // 获取当前实例
// 如果实例不存在,则创建一个新的实例
if (!self) {
self = new this.constructor(obj.currentTarget, this.getDelegateOptions());
$(obj.currentTarget).data('bs.' + this.type, self); // 保存实例
}
if (obj instanceof $.Event) {
self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false; // 更新焦点或悬停状态
}
// 如果有其他激活状态,则不执行隐藏
if (self.isInStateTrue()) return;
clearTimeout(self.timeout); // 清除超时
self.hoverState = 'out'; // 设置为离开状态
// 如果没有延时,立即隐藏;否则设置延时隐藏
if (!self.options.delay || !self.options.delay.hide) return self.hide();
self.timeout = setTimeout(function () {
if (self.hoverState == 'out') self.hide(); // 延时隐藏
}, self.options.delay.hide);
}
// 显示 Tooltip
Tooltip.prototype.show = function () {
var e = $.Event('show.bs.' + this.type); // 创建显示事件
// 如果 Tooltip 内容存在且启用,触发显示事件
if (this.hasContent() && this.enabled) {
this.$element.trigger(e); // 触发事件
var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]); // 判断元素是否在 DOM 中
if (e.isDefaultPrevented() || !inDom) return; // 如果事件被阻止或元素不在 DOM 中,返回
var that = this;
var $tip = this.tip(); // 获取 Tooltip DOM 元素
var tipId = this.getUID(this.type); // 获取唯一 ID
this.setContent(); // 设置内容
$tip.attr('id', tipId); // 设置 ID
this.$element.attr('aria-describedby', tipId); // 设置 ARIA 属性
if (this.options.animation) $tip.addClass('fade'); // 如果启用动画,添加 'fade' 类
var placement = typeof this.options.placement == 'function' ? // 获取位置
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement;
var autoToken = /\s?auto?\s?/i; // 自动位置标记
var autoPlace = autoToken.test(placement); // 是否需要自动位置
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'; // 处理自动位置
// 移除旧的 Tooltip设置新的显示位置
$tip
.detach()
.css({ top: 0, left: 0, display: 'block' })
.addClass(placement)
.data('bs.' + this.type, this);
// 如果指定了容器,则将 Tooltip 插入到容器中,否则直接插入到元素之后
this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element);
this.$element.trigger('inserted.bs.' + this.type); // 触发插入事件
var pos = this.getPosition(); // 获取位置
var actualWidth = $tip[0].offsetWidth; // 获取实际宽度
var actualHeight = $tip[0].offsetHeight; // 获取实际高度
// 根据位置调整 Tooltip 的显示位置
if (autoPlace) {
var orgPlacement = placement; // 保存原始位置
var viewportDim = this.getPosition(this.$viewport); // 获取视口尺寸
// 计算 Tooltip 应该显示的位置
placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement;
$tip
.removeClass(orgPlacement)
.addClass(placement); // 设置新的位置
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
// 应用计算出的偏移量来定位tooltip
this.applyPlacement(calculatedOffset, placement)
// 完成动画后的回调函数
var complete = function () {
// 保存当前hover状态
var prevHoverState = that.hoverState
// 触发tooltip显示事件
that.$element.trigger('shown.bs.' + that.type)
// 重置hoverState状态
that.hoverState = null
// 如果之前的hover状态是'out'则调用leave方法来隐藏tooltip
if (prevHoverState == 'out') that.leave(that)
}
// 检查是否支持过渡动画且tooltip有fade类
// 如果支持过渡,监听过渡结束事件
$.support.transition && this.$tip.hasClass('fade') ?
// 在过渡结束时执行complete函数
$tip
.one('bsTransitionEnd', complete) // 监听过渡结束事件
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : // 模拟过渡效果
complete()
}
}
Tooltip.prototype.applyPlacement = function (offset, placement) {
// 获取tooltip元素
var $tip = this.tip()
// 获取tooltip的实际宽度和高度
var width = $tip[0].offsetWidth
var height = $tip[0].offsetHeight
// 手动读取tooltip的margin值因为getBoundingClientRect包含了边距
var marginTop = parseInt($tip.css('margin-top'), 10)
var marginLeft = parseInt($tip.css('margin-left'), 10)
// 兼容IE8/9检查margin值是否为NaN
if (isNaN(marginTop)) marginTop = 0
if (isNaN(marginLeft)) marginLeft = 0
// 更新偏移量考虑到margin
offset.top += marginTop
offset.left += marginLeft
// 使用setOffset方法直接应用计算出的偏移量
// $.fn.offset()不会四舍五入像素值因此我们使用setOffset进行自定义
$.offset.setOffset($tip[0], $.extend({
using: function (props) {
// 四舍五入计算的top和left值
$tip.css({
top: Math.round(props.top),
left: Math.round(props.left)
})
}
}, offset), 0)
$tip.addClass('in')
// 检查重新定位后的tooltip是否因偏移量变化而导致尺寸改变
var actualWidth = $tip[0].offsetWidth // 获取tooltip实际宽度
var actualHeight = $tip[0].offsetHeight // 获取tooltip实际高度
// 如果placement是'top'且tooltip的实际高度与原始高度不同调整top偏移量
if (placement == 'top' && actualHeight != height) {
offset.top = offset.top + height - actualHeight
}
// 获取根据视口调整后的偏移量,考虑到可能的边界限制
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
// 根据delta值调整位置优先调整left如果没有则调整top
if (delta.left) offset.left += delta.left
else offset.top += delta.top
// 判断placement是否是垂直方向的top或bottom
var isVertical = /top|bottom/.test(placement)
// 计算箭头的位置偏移量delta值的2倍减去宽度/高度差值
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
// 确定箭头应该参考的维度,是宽度还是高度
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
// 根据计算出的偏移量设置tooltip的位置
$tip.offset(offset)
// 调用replaceArrow方法来调整箭头的指向
this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
javascript
复制代码
// 替换箭头的位置
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
this.arrow()
// 根据是否是垂直方向设置箭头的位置left或top
.css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
// 清空另一个方向的样式
.css(isVertical ? 'top' : 'left', '')
}
javascript
复制代码
// 设置tooltip的内容
Tooltip.prototype.setContent = function () {
var $tip = this.tip() // 获取tooltip元素
var title = this.getTitle() // 获取tooltip的标题
// 根据options设置tooltip的内容支持html或text
$tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
// 移除所有位置相关的class
$tip.removeClass('fade in top bottom left right')
}
javascript
复制代码
// 隐藏tooltip并执行回调函数
Tooltip.prototype.hide = function (callback) {
var that = this
var $tip = $(this.$tip)
var e = $.Event('hide.bs.' + this.type)
function complete() {
// 如果hoverState不是'in'移除tooltip
if (that.hoverState != 'in') $tip.detach()
// 如果$element存在移除aria-describedby属性并触发'hidden'事件
if (that.$element) {
that.$element
.removeAttr('aria-describedby')
.trigger('hidden.bs.' + that.type)
}
// 执行回调函数
callback && callback()
}
// 触发'event'事件
this.$element.trigger(e)
// 如果事件被阻止,直接返回
if (e.isDefaultPrevented()) return
// 移除'in'类
$tip.removeClass('in')
// 如果支持过渡效果且有fade类使用过渡效果否则直接执行complete函数
$.support.transition && $tip.hasClass('fade') ?
$tip
.one('bsTransitionEnd', complete) // 在过渡结束后执行complete
.emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : // 模拟过渡时间
complete() // 直接执行complete
}
javascript
// 将hoverState设置为nul
this.hoverState = null
return this
}
Tooltip.prototype.fixTitle = function () {
var $e = this.$element
if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
// 如果title存在则将其存入data-original-title中并清空title属性
$e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
}
}
javascript
复制代码
// 判断tooltip是否有内容
Tooltip.prototype.hasContent = function () {
return this.getTitle() // 通过getTitle()方法获取title
}
javascript
复制代码
// 获取元素的位置信息
Tooltip.prototype.getPosition = function ($element) {
$element = $element || this.$element
var el = $element[0]
var isBody = el.tagName == 'BODY'
var elRect = el.getBoundingClientRect() // 获取元素的矩形边界
if (elRect.width == null) {
// 如果IE8没有width和height手动计算
elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
}
var isSvg = window.SVGElement && el instanceof window.SVGElement
// 对SVG元素使用$.offset()会有问题避免在SVG上使用
var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
return $.extend({}, elRect, scroll, outerDims, elOffset) // 返回合并后的位置信息
}
javascript
复制代码
// 根据placement、位置、实际宽高计算偏移量
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
}
javascript
复制代码
// 获取视口调整后的偏移量,避免溢出视口
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 }
if (!this.$viewport) return delta // 如果没有viewport返回默认的delta
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
var viewportDimensions = this.getPosition(this.$viewport)
if (/right|left/.test(placement)) {
// 处理左右位置溢出的情况
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
if (topEdgeOffset < viewportDimensions.top) { // top溢出
delta.top = viewportDimensions.top - topEdgeOffset
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom溢出
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
}
} else {
// 处理上下位置溢出的情况
var leftEdgeOffset = pos.left - viewportPadding
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left溢出
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right溢出
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
return delta // 返回调整后的delta
}
javascript
复制代码
// 获取tooltip的标题
Tooltip.prototype.getTitle = function () {
var title
var $e = this.$element
var o = this.options
// 获取data-original-title或options中的title
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
return title // 返回title
}
javascript
复制代码
// 生成唯一的ID
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000) // 生成随机数并拼接前缀
while (document.getElementById(prefix)) // 确保ID唯一
return prefix // 返回生成的唯一ID
}
javascript
复制代码
// 获取tooltip元素
Tooltip.prototype.tip = function () {
if (!this.$tip) {
this.$tip = $(this.options.template) // 根据template选项生成tooltip元素
if (this.$tip.length != 1) {
throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
}
}
return this.$tip // 返回tooltip元素
}
javascript
复制代码
// 获取tooltip的箭头元素
Tooltip.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
}
javascript
复制代码
// 启用tooltip
Tooltip.prototype.enable = function () {
this.enabled = true
}
javascript
复制代码
// 禁用tooltip
Tooltip.prototype.disable = function () {
this.enabled = false
}
javascript
复制代码
// 切换tooltip的启用状态
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled
}
javascript
复制代码
// 切换tooltip的显示状态
Tooltip.prototype.toggle = function (e) {
var self = this
if (e) {
self = $(e.currentTarget).data('bs.' + this.type) // 获取当前实例
if (!self) {
self = new this.constructor(e.currentTarget, this.getDelegateOptions()) // 创建新实例
$(e.currentTarget).data('bs.' + this.type, self)
}
}
if (e) {
// 切换click状态并根据状态显示或隐藏tooltip
self.inState.click = !self.inState.click
if (self.isInStateTrue()) self.enter(self)
else self.leave(self)
} else {
self.tip().hasClass('in') ? self.leave(self) : self.enter(self) // 根据是否显示切换tooltip状态
}
}
javascript
复制代码
// 销毁tooltip实例
Tooltip.prototype.destroy = function () {
var that = this
clearTimeout(this.timeout) // 清除超时
this.hide(function () {
// 移除事件监听和数据
that.$element.off('.' + that.type).removeData('bs.' + that.type)
if (that.$tip) {
that.$tip.detach() // 从DOM中移除tooltip
}
that.$tip = null
that.$arrow = null
that.$viewport = null
that.$element = null
})
}
// TOOLTIP PLUGIN DEFINITION
// =========================
// 定义Plugin函数接受option参数用于在每个元素上执行tooltip插件
function Plugin(option) {
return this.each(function () {
var $this = $(this) // 获取当前元素的jQuery对象
var data = $this.data('bs.tooltip') // 获取当前元素的tooltip实例
var options = typeof option == 'object' && option // 如果传入的是对象则赋值给options
if (!data && /destroy|hide/.test(option)) return // 如果没有tooltip实例且option是destroy或hide则直接返回
if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) // 如果没有tooltip实例创建新的tooltip实例并绑定到元素
if (typeof option == 'string') data[option]() // 如果option是字符串调用对应的tooltip方法
})
}
// 保存之前的tooltip插件实现
var old = $.fn.tooltip
// 将Plugin函数赋值给jQuery的tooltip插件使其生效
$.fn.tooltip = Plugin
$.fn.tooltip.Constructor = Tooltip // 将Tooltip构造函数挂载到jQuery对象上
// TOOLTIP NO CONFLICT
// ===================
// 恢复之前的tooltip插件实现避免与其他插件冲突
$.fn.tooltip.noConflict = function () {
$.fn.tooltip = old // 恢复原插件
return this // 返回当前jQuery对象
}
}(jQuery); // 结束jQuery的闭包
/* ========================================================================
* Bootstrap: popover.js v3.3.7
* http://getbootstrap.com/javascript/#popovers
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// POPOVER PUBLIC CLASS DEFINITION
// ===============================
// 定义Popover类继承自tooltip类
var Popover = function (element, options) {
this.init('popover', element, options) // 调用初始化方法传入popover类型
}
// 检查是否存在tooltip如果不存在则抛出错误
if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
// 设置Popover的版本号
Popover.VERSION = '3.3.7'
// 定义Popover的默认配置项继承tooltip的默认配置并自定义一些属性
Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
placement: 'right', // 设置默认的显示位置为右侧
trigger: 'click', // 设置触发方式为点击
content: '', // 设置默认内容为空
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' // 默认的Popover模板
})
// 说明Popover继承了tooltip.js的功能
// ================================
// 使用extend方法继承tooltip的构造函数原型
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
// 设置Popover构造函数
Popover.prototype.constructor = Popover
// 获取Popover的默认配置
Popover.prototype.getDefaults = function () {
return Popover.DEFAULTS
}
// 设置Popover的内容
Popover.prototype.setContent = function () {
var $tip = this.tip() // 获取Popover元素
var title = this.getTitle() // 获取Popover的标题
var content = this.getContent() // 获取Popover的内容
// 设置Popover的标题和内容
$tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
$tip.find('.popover-content').children().detach().end()[
this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
](content)
// 移除不需要的CSS类
$tip.removeClass('fade top bottom left right in')
// 兼容IE8的处理如果Popover的标题为空手动隐藏标题
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
}
// 检查Popover是否有内容
Popover.prototype.hasContent = function () {
return this.getTitle() || this.getContent()
}
// 获取Popover的内容
Popover.prototype.getContent = function () {
var $e = this.$element
var o = this.options
return $e.attr('data-content') // 首先检查data-content属性
|| (typeof o.content == 'function' ? // 如果配置项content是一个函数调用该函数
o.content.call($e[0]) :
o.content) // 否则返回配置项content的值
}
// 获取Popover的箭头元素
Popover.prototype.arrow = function () {
return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
}
// POPOVER PLUGIN DEFINITION
// =========================
// 定义Popover插件
function Plugin(option) {
return this.each(function () {
var $this = $(this) // 获取当前元素的jQuery对象
var data = $this.data('bs.popover') // 获取Popover实例
var options = typeof option == 'object' && option // 如果传入的option是对象则赋值给options
// 如果没有Popover实例并且传入的option是destroy或hide则直接返回
if (!data && /destroy|hide/.test(option)) return
if (!data) $this.data('bs.popover', (data = new Popover(this, options))) // 如果没有Popover实例创建新的Popover实例并绑定到元素
if (typeof option == 'string') data[option]() // 如果option是字符串调用对应的方法
})
}
// 保存原有的Popover插件实现
var old = $.fn.popover
// 将Plugin函数赋值给jQuery的popover插件
$.fn.popover = Plugin
$.fn.popover.Constructor = Popover // 将Popover构造函数挂载到jQuery对象上
// POPOVER NO CONFLICT
// ===================
// 恢复原有的Popover插件实现避免与其他插件冲突
$.fn.popover.noConflict = function () {
$.fn.popover = old // 恢复原插件
return this // 返回当前jQuery对象
}
}(jQuery); // 结束jQuery的闭包
/* ========================================================================
* Bootstrap: scrollspy.js v3.3.7
* http://getbootstrap.com/javascript/#scrollspy
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// SCROLLSPY CLASS DEFINITION
// ==========================
// ScrollSpy类的构造函数
function ScrollSpy(element, options) {
this.$body = $(document.body) // 获取document.body
this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) // 设置滚动元素如果是body则使用window
this.options = $.extend({}, ScrollSpy.DEFAULTS, options) // 合并默认配置和用户配置
this.selector = (this.options.target || '') + ' .nav li > a' // 目标选择器
this.offsets = [] // 存储偏移量
this.targets = [] // 存储目标元素
this.activeTarget = null // 当前激活的目标
this.scrollHeight = 0 // 当前滚动区域的高度
// 绑定滚动事件
this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
this.refresh() // 初始化时刷新偏移量和目标元素
this.process() // 初始化时调用处理函数
}
// 设置ScrollSpy的版本号
ScrollSpy.VERSION = '3.3.7'
// ScrollSpy的默认配置
ScrollSpy.DEFAULTS = {
offset: 10 // 设置偏移量默认为10
}
// 获取滚动区域的总高度
ScrollSpy.prototype.getScrollHeight = function () {
return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
}
// 刷新偏移量和目标元素
ScrollSpy.prototype.refresh = function () {
var that = this
var offsetMethod = 'offset' // 默认使用offset获取位置
var offsetBase = 0 // 初始偏移量
this.offsets = [] // 清空偏移量数组
this.targets = [] // 清空目标数组
this.scrollHeight = this.getScrollHeight() // 获取当前滚动区域高度
if (!$.isWindow(this.$scrollElement[0])) { // 如果不是window使用position方法
offsetMethod = 'position'
offsetBase = this.$scrollElement.scrollTop()
}
// 查找所有目标元素,并计算每个目标元素的偏移量
this.$body
.find(this.selector)
.map(function () {
var $el = $(this) // 当前元素
var href = $el.data('target') || $el.attr('href') // 获取目标的ID
var $href = /^#./.test(href) && $(href) // 如果目标是一个有效的ID查找该元素
// 返回目标元素的偏移量和href
return ($href
&& $href.length
&& $href.is(':visible')
&& [[$href[offsetMethod]().top + offsetBase, href]]) || null
})
.sort(function (a, b) { return a[0] - b[0] }) // 按照偏移量升序排序
.each(function () {
that.offsets.push(this[0]) // 存储偏移量
that.targets.push(this[1]) // 存储目标
})
}
// 处理滚动事件,检查当前滚动位置并激活相应目标
ScrollSpy.prototype.process = function () {
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset // 获取当前滚动位置
var scrollHeight = this.getScrollHeight() // 获取滚动区域总高度
var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() // 计算最大滚动高度
var offsets = this.offsets // 偏移量数组
var targets = this.targets // 目标数组
var activeTarget = this.activeTarget // 当前激活的目标
var i
if (this.scrollHeight != scrollHeight) { // 如果滚动区域高度发生变化,刷新
this.refresh()
}
if (scrollTop >= maxScroll) { // 如果滚动到达最大位置,激活最后一个目标
return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
}
if (activeTarget && scrollTop < offsets[0]) { // 如果当前激活的目标在最上方,清除激活状态
this.activeTarget = null
return this.clear()
}
// 检查每个目标元素,判断是否需要激活
for (i = offsets.length; i--;) {
activeTarget != targets[i] // 如果目标不等于当前激活的目标
&& scrollTop >= offsets[i] // 且滚动位置大于目标的偏移量
&& (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) // 且滚动位置小于下一个目标的偏移量
&& this.activate(targets[i]) // 激活该目标
}
}
// 激活目标
ScrollSpy.prototype.activate = function (target) {
this.activeTarget = target // 设置当前激活的目标
this.clear() // 清除其他目标的激活状态
var selector = this.selector +
'[data-target="' + target + '"],' +
this.selector + '[href="' + target + '"]' // 根据目标生成选择器
var active = $(selector) // 查找对应目标的元素
.parents('li') // 获取父级li元素
.addClass('active') // 给父级li添加active类
// 如果父级是dropdown-menu则激活最近的父级li.dropdown
if (active.parent('.dropdown-menu').length) {
active = active
.closest('li.dropdown')
.addClass('active')
}
active.trigger('activate.bs.scrollspy') // 触发激活事件
}
// 清除所有激活状态
ScrollSpy.prototype.clear = function () {
$(this.selector)
.parentsUntil(this.options.target, '.active')
.removeClass('active') // 移除所有激活的类
}
// SCROLLSPY PLUGIN DEFINITION
// ===========================
// 定义ScrollSpy插件
function Plugin(option) {
return this.each(function () {
var $this = $(this) // 获取当前元素
var data = $this.data('bs.scrollspy') // 获取ScrollSpy实例
var options = typeof option == 'object' && option // 如果option是对象则赋值给options
if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) // 如果没有ScrollSpy实例创建新的实例并绑定
if (typeof option == 'string') data[option]() // 如果option是字符串调用对应的方法
})
}
// 保存原有的ScrollSpy插件实现
var old = $.fn.scrollspy
// 将Plugin函数赋值给jQuery的scrollspy插件
$.fn.scrollspy = Plugin
$.fn.scrollspy.Constructor = ScrollSpy // 将ScrollSpy构造函数挂载到jQuery对象上
// SCROLLSPY NO CONFLICT
// =====================
// 恢复原有的ScrollSpy插件实现避免与其他插件冲突
$.fn.scrollspy.noConflict = function () {
$.fn.scrollspy = old // 恢复原插件
return this // 返回当前jQuery对象
}
// SCROLLSPY DATA-API
// ==================
// 当页面加载完毕初始化所有data-spy="scroll"的元素
$(window).on('load.bs.scrollspy.data-api', function () {
$('[data-spy="scroll"]').each(function () {
var $spy = $(this)
Plugin.call($spy, $spy.data()) // 调用插件
})
})
}(jQuery); // 结束jQuery的闭包
/* ========================================================================
* Bootstrap: tab.js v3.3.7
* http://getbootstrap.com/javascript/#tabs
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// TAB CLASS DEFINITION
// ====================
// Tab类的构造函数
var Tab = function (element) {
// jscs:disable requireDollarBeforejQueryAssignment
this.element = $(element) // 将元素封装成jQuery对象
// jscs:enable requireDollarBeforejQueryAssignment
}
// 设置Tab版本
Tab.VERSION = '3.3.7'
// Tab的过渡时长
Tab.TRANSITION_DURATION = 150
// 显示Tab
Tab.prototype.show = function () {
var $this = this.element // 获取Tab元素
var $ul = $this.closest('ul:not(.dropdown-menu)') // 获取Tab所在的ul元素
var selector = $this.data('target') // 获取目标选择器
// 如果没有指定目标选择器从href属性获取
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // 去掉前缀兼容ie7
}
// 如果当前Tab已激活则不进行任何操作
if ($this.parent('li').hasClass('active')) return
var $previous = $ul.find('.active:last a') // 获取当前激活Tab
var hideEvent = $.Event('hide.bs.tab', {
relatedTarget: $this[0]
})
var showEvent = $.Event('show.bs.tab', {
relatedTarget: $previous[0]
})
// 触发hide和show事件
$previous.trigger(hideEvent)
$this.trigger(showEvent)
// 如果有任何一个事件被取消,停止执行
if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
var $target = $(selector) // 获取目标Tab元素
// 激活Tab
this.activate($this.closest('li'), $ul)
this.activate($target, $target.parent(), function () {
// 触发Tab切换后的事件
$previous.trigger({
type: 'hidden.bs.tab',
relatedTarget: $this[0]
})
$this.trigger({
type: 'shown.bs.tab',
relatedTarget: $previous[0]
})
})
}
// 激活指定的Tab
Tab.prototype.activate = function (element, container, callback) {
var $active = container.find('> .active') // 获取当前激活的Tab
var transition = callback
&& $.support.transition
&& ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
// 激活Tab的函数
function next() {
// 清除当前Tab的激活状态
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', false)
// 设置目标Tab为激活状态
element
.addClass('active')
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
// 如果有过渡效果,添加'in'类以触发过渡动画
if (transition) {
element[0].offsetWidth // 强制回流以触发过渡
element.addClass('in')
} else {
element.removeClass('fade')
}
// 如果Tab在dropdown菜单中则处理父级元素
if (element.parent('.dropdown-menu').length) {
element
.closest('li.dropdown')
.addClass('active')
.end()
.find('[data-toggle="tab"]')
.attr('aria-expanded', true)
}
callback && callback() // 调用回调函数
}
// 如果存在过渡效果,绑定'bsTransitionEnd'事件来完成动画
$active.length && transition ?
$active
.one('bsTransitionEnd', next)
.emulateTransitionEnd(Tab.TRANSITION_DURATION) :
next()
// 移除过渡的'in'类
$active.removeClass('in')
}
// TAB PLUGIN DEFINITION
// =====================
// 定义Tab插件
function Plugin(option) {
return this.each(function () {
var $this = $(this) // 获取当前元素
var data = $this.data('bs.tab') // 获取Tab实例
var options = typeof option == 'object' && option // 如果option是对象则赋值给options
// 如果没有Tab实例则创建一个新的Tab实例
if (!data) $this.data('bs.tab', (data = new Tab(this)))
// 如果option是字符串调用对应的方法
if (typeof option == 'string') data[option]()
})
}
// 保存原来的Tab插件实现
var old = $.fn.tab
// 将Plugin函数赋值给jQuery的tab插件
$.fn.tab = Plugin
$.fn.tab.Constructor = Tab // 将Tab构造函数挂载到jQuery对象上
// TAB NO CONFLICT
// ===============
// 恢复原有的Tab插件实现避免与其他插件冲突
$.fn.tab.noConflict = function () {
$.fn.tab = old // 恢复原插件
return this // 返回当前jQuery对象
}
// TAB DATA-API
// ============
// 处理Tab的点击事件触发show方法
var clickHandler = function (e) {
e.preventDefault() // 阻止默认事件
Plugin.call($(this), 'show') // 调用show方法显示对应Tab
}
// 绑定事件点击Tab时触发相应的show事件
$(document)
.on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
.on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
}(jQuery);
/* ========================================================================
* Bootstrap: affix.js v3.3.7
* http://getbootstrap.com/javascript/#affix
* ========================================================================
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// AFFIX CLASS DEFINITION
// ======================
// Affix类的构造函数
var Affix = function (element, options) {
this.options = $.extend({}, Affix.DEFAULTS, options)
this.$target = $(this.options.target) // 绑定目标通常是window或document
.on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) // 监听滚动事件
.on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) // 监听点击事件
this.$element = $(element) // 绑定目标元素
this.affixed = null // 当前固定状态
this.unpin = null // 存储解除固定的偏移量
this.pinnedOffset = null // 存储固定偏移量
this.checkPosition() // 初始化时检查位置
}
// 设置Affix版本
Affix.VERSION = '3.3.7'
// Affix的重置类名
Affix.RESET = 'affix affix-top affix-bottom'
// Affix的默认配置
Affix.DEFAULTS = {
offset: 0, // 偏移量
target: window // 监听目标默认是window
}
// 获取元素的状态(例如是否需要固定)
Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
var scrollTop = this.$target.scrollTop() // 获取滚动距离
var position = this.$element.offset() // 获取元素位置
var targetHeight = this.$target.height() // 获取目标高度
// 判断是否固定在顶部
if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
// 判断是否固定在底部
if (this.affixed == 'bottom') {
if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
}
var initializing = this.affixed == null
var colliderTop = initializing ? scrollTop : position.top
var colliderHeight = initializing ? targetHeight : height
if (offsetTop != null && scrollTop <= offsetTop) return 'top'
if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
return false
}
// 获取固定偏移量
Affix.prototype.getPinnedOffset = function () {
if (this.pinnedOffset) return this.pinnedOffset
this.$element.removeClass(Affix.RESET).addClass('affix')
var scrollTop = this.$target.scrollTop()
var position = this.$element.offset()
return (this.pinnedOffset = position.top - scrollTop)
}
// 定期检查位置
Affix.prototype.checkPositionWithEventLoop = function () {
setTimeout($.proxy(this.checkPosition, this), 1)
}
// 检查元素的固定位置
Affix.prototype.checkPosition = function () {
if (!this.$element.is(':visible')) return
var height = this.$element.height()
var offset = this.options.offset
var offsetTop = offset.top
var offsetBottom = offset.bottom
var scrollHeight = Math.max($(document).height(), $(document.body).height())
if (typeof offset != 'object') offsetBottom = offsetTop = offset
if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
if (this.affixed != affix) {
if (this.unpin != null) this.$element.css('top', '')
var affixType = 'affix' + (affix ? '-' + affix : '')
var e = $.Event(affixType + '.bs.affix')
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
this.affixed = affix
this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
this.$element
.removeClass(Affix.RESET)
.addClass(affixType)
.trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
}
if (affix == 'bottom') {
this.$element.offset({
top: scrollHeight - height - offsetBottom
})
}
}
// AFFIX PLUGIN DEFINITION
// =======================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.affix')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.affix
$.fn.affix = Plugin
$.fn.affix.Constructor = Affix
// AFFIX NO CONFLICT
// =================
$.fn.affix.noConflict = function () {
$.fn.affix = old
return this
}
// AFFIX DATA-API
// ==============
// 初始化affix功能绑定到窗口加载事件
$(window).on('load', function () {
$('[data-spy="affix"]').each(function () {
var $spy = $(this)
var data = $spy.data()
data.offset = data.offset || {}
if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
if (data.offsetTop != null) data.offset.top = data.offsetTop
Plugin.call($spy, data)
})
})
}(jQuery);