aboutsummaryrefslogtreecommitdiff
path: root/js/scrollspy.js
diff options
context:
space:
mode:
authorfat <[email protected]>2015-01-03 13:58:44 -0800
committerfat <[email protected]>2015-02-11 11:29:43 -0800
commit834220ea20ce5b7cd31edfb624a28b4bf8b29a6a (patch)
tree19bfdadb0c140df437e7b7034e5e1f5ce58343cc /js/scrollspy.js
parentaed1cd31218113d67d2eca3296edf5d1700b19b8 (diff)
downloadbootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.tar.xz
bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.zip
bootstrap onto closure
Diffstat (limited to 'js/scrollspy.js')
-rw-r--r--js/scrollspy.js427
1 files changed, 299 insertions, 128 deletions
diff --git a/js/scrollspy.js b/js/scrollspy.js
index 0987177fd..150241b54 100644
--- a/js/scrollspy.js
+++ b/js/scrollspy.js
@@ -1,175 +1,346 @@
-/* ========================================================================
- * Bootstrap: scrollspy.js v3.3.2
+/** =======================================================================
+ * Bootstrap: scrollspy.js v4.0.0
* http://getbootstrap.com/javascript/#scrollspy
* ========================================================================
* Copyright 2011-2015 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
+ * ========================================================================
+ * @fileoverview - Bootstrap's scrollspy plugin.
+ *
+ * Public Methods & Properties:
+ *
+ * + $.scrollspy
+ * + $.scrollspy.noConflict
+ * + $.scrollspy.Constructor
+ * + $.scrollspy.Constructor.VERSION
+ * + $.scrollspy.Constructor.Defaults
+ * + $.scrollspy.Constructor.Defaults.offset
+ * + $.scrollspy.Constructor.prototype.refresh
+ *
+ * ========================================================================
+ */
+'use strict';
-+function ($) {
- 'use strict';
- // SCROLLSPY CLASS DEFINITION
- // ==========================
+/**
+ * Our scrollspy class.
+ * @param {Element!} element
+ * @param {Object=} opt_config
+ * @constructor
+ */
+function ScrollSpy(element, opt_config) {
- function ScrollSpy(element, options) {
- var process = $.proxy(this.process, this)
+ /** @private {Element|Window} */
+ this._scrollElement = element.tagName == 'BODY' ? window : element
- this.$body = $('body')
- this.$scrollElement = $(element).is('body') ? $(window) : $(element)
- this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
- this.selector = (this.options.target || '') + ' .nav li > a'
- this.offsets = []
- this.targets = []
- this.activeTarget = null
- this.scrollHeight = 0
+ /** @private {Object} */
+ this._config = $.extend({}, ScrollSpy['Defaults'], opt_config)
- this.$scrollElement.on('scroll.bs.scrollspy', process)
- this.refresh()
- this.process()
- }
+ /** @private {string} */
+ this._selector = (this._config.target || '') + ' .nav li > a'
- ScrollSpy.VERSION = '3.3.2'
+ /** @private {Array} */
+ this._offsets = []
- ScrollSpy.DEFAULTS = {
- offset: 10
- }
+ /** @private {Array} */
+ this._targets = []
- ScrollSpy.prototype.getScrollHeight = function () {
- return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
- }
+ /** @private {Element} */
+ this._activeTarget = null
- ScrollSpy.prototype.refresh = function () {
- var offsetMethod = 'offset'
- var offsetBase = 0
+ /** @private {number} */
+ this._scrollHeight = 0
- if (!$.isWindow(this.$scrollElement[0])) {
- offsetMethod = 'position'
- offsetBase = this.$scrollElement.scrollTop()
- }
+ $(this._scrollElement).on('scroll.bs.scrollspy', this._process.bind(this))
- this.offsets = []
- this.targets = []
- this.scrollHeight = this.getScrollHeight()
-
- var self = this
-
- this.$body
- .find(this.selector)
- .map(function () {
- var $el = $(this)
- var href = $el.data('target') || $el.attr('href')
- var $href = /^#./.test(href) && $(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 () {
- self.offsets.push(this[0])
- self.targets.push(this[1])
- })
- }
+ this['refresh']()
- 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()
- }
+ this._process()
+}
- if (scrollTop >= maxScroll) {
- return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
- }
- if (activeTarget && scrollTop < offsets[0]) {
- this.activeTarget = null
- return this.clear()
+/**
+ * @const
+ * @type {string}
+ */
+ScrollSpy['VERSION'] = '4.0.0'
+
+
+/**
+ * @const
+ * @type {Object}
+ */
+ScrollSpy['Defaults'] = {
+ 'offset': 10
+}
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ScrollSpy._NAME = 'scrollspy'
+
+
+/**
+ * @const
+ * @type {string}
+ * @private
+ */
+ScrollSpy._DATA_KEY = 'bs.scrollspy'
+
+
+/**
+ * @const
+ * @type {Function}
+ * @private
+ */
+ScrollSpy._JQUERY_NO_CONFLICT = $.fn[ScrollSpy._NAME]
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+ScrollSpy._Event = {
+ ACTIVATE: 'activate.bs.scrollspy'
+}
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+ScrollSpy._ClassName = {
+ DROPDOWN_MENU : 'dropdown-menu',
+ ACTIVE : 'active'
+}
+
+
+/**
+ * @const
+ * @enum {string}
+ * @private
+ */
+ScrollSpy._Selector = {
+ DATA_SPY : '[data-spy="scroll"]',
+ ACTIVE : '.active',
+ LI_DROPDOWN : 'li.dropdown',
+ LI : 'li'
+}
+
+
+/**
+ * @param {Object=} opt_config
+ * @this {jQuery}
+ * @return {jQuery}
+ * @private
+ */
+ScrollSpy._jQueryInterface = function (opt_config) {
+ return this.each(function () {
+ var data = $(this).data(ScrollSpy._DATA_KEY)
+ var config = typeof opt_config === 'object' && opt_config || null
+
+ if (!data) {
+ data = new ScrollSpy(this, config)
+ $(this).data(ScrollSpy._DATA_KEY, data)
}
- for (i = offsets.length; i--;) {
- activeTarget != targets[i]
- && scrollTop >= offsets[i]
- && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
- && this.activate(targets[i])
+ if (typeof opt_config === 'string') {
+ data[opt_config]()
}
+ })
+}
+
+
+/**
+ * Refresh the scrollspy target cache
+ */
+ScrollSpy.prototype['refresh'] = function () {
+ var offsetMethod = 'offset'
+ var offsetBase = 0
+
+ if (this._scrollElement !== this._scrollElement.window) {
+ offsetMethod = 'position'
+ offsetBase = this._getScrollTop()
}
- ScrollSpy.prototype.activate = function (target) {
- this.activeTarget = target
+ this._offsets = []
+ this._targets = []
- this.clear()
+ this._scrollHeight = this._getScrollHeight()
- var selector = this.selector +
- '[data-target="' + target + '"],' +
- this.selector + '[href="' + target + '"]'
+ var targets = /** @type {Array.<Element>} */ ($.makeArray($(this._selector)))
- var active = $(selector)
- .parents('li')
- .addClass('active')
+ targets
+ .map(function (element, index) {
+ var target
+ var targetSelector = Bootstrap.getSelectorFromElement(element)
- if (active.parent('.dropdown-menu').length) {
- active = active
- .closest('li.dropdown')
- .addClass('active')
+ if (targetSelector) {
+ target = $(targetSelector)[0]
+ }
+
+ if (target && (target.offsetWidth || target.offsetHeight)) {
+ // todo (fat): remove sketch reliance on jQuery position/offset
+ return [$(target)[offsetMethod]().top + offsetBase, targetSelector]
+ }
+ })
+ .filter(function (item) { return item })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .forEach(function (item, index) {
+ this._offsets.push(item[0])
+ this._targets.push(item[1])
+ }.bind(this))
+}
+
+
+/**
+ * @private
+ */
+ScrollSpy.prototype._getScrollTop = function () {
+ return this._scrollElement === window ?
+ this._scrollElement.scrollY : this._scrollElement.scrollTop
+}
+
+
+/**
+ * @private
+ */
+ScrollSpy.prototype._getScrollHeight = function () {
+ return this._scrollElement.scrollHeight
+ || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
+}
+
+
+/**
+ * @private
+ */
+ScrollSpy.prototype._process = function () {
+ var scrollTop = this._getScrollTop() + this._config.offset
+ var scrollHeight = this._getScrollHeight()
+ var maxScroll = this._config.offset + scrollHeight - this._scrollElement.offsetHeight
+
+ if (this._scrollHeight != scrollHeight) {
+ this['refresh']()
+ }
+
+ if (scrollTop >= maxScroll) {
+ var target = this._targets[this._targets.length - 1]
+
+ if (this._activeTarget != target) {
+ this._activate(target)
}
+ }
- active.trigger('activate.bs.scrollspy')
+ if (this._activeTarget && scrollTop < this._offsets[0]) {
+ this._activeTarget = null
+ this._clear()
+ return
}
- ScrollSpy.prototype.clear = function () {
- $(this.selector)
- .parentsUntil(this.options.target, '.active')
- .removeClass('active')
+ for (var i = this._offsets.length; i--;) {
+ var isActiveTarget = this._activeTarget != this._targets[i]
+ && scrollTop >= this._offsets[i]
+ && (!this._offsets[i + 1] || scrollTop < this._offsets[i + 1])
+
+ if (isActiveTarget) {
+ this._activate(this._targets[i])
+ }
}
+}
- // SCROLLSPY PLUGIN DEFINITION
- // ===========================
+/**
+ * @param {Element} target
+ * @private
+ */
+ScrollSpy.prototype._activate = function (target) {
+ this._activeTarget = target
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.scrollspy')
- var options = typeof option == 'object' && option
+ this._clear()
- if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
+ var selector = this._selector
+ + '[data-target="' + target + '"],'
+ + this._selector + '[href="' + target + '"]'
+
+ // todo (fat): this seems horribly wrong… getting all raw li elements up the tree ,_,
+ var parentListItems = $(selector).parents(ScrollSpy._Selector.LI)
+
+ for (var i = parentListItems.length; i--;) {
+ $(parentListItems[i]).addClass(ScrollSpy._ClassName.ACTIVE)
+
+ var itemParent = parentListItems[i].parentNode
- var old = $.fn.scrollspy
+ if (itemParent && $(itemParent).hasClass(ScrollSpy._ClassName.DROPDOWN_MENU)) {
+ var closestDropdown = $(itemParent).closest(ScrollSpy._Selector.LI_DROPDOWN)[0]
+ $(closestDropdown).addClass(ScrollSpy._ClassName.ACTIVE)
+ }
+ }
- $.fn.scrollspy = Plugin
- $.fn.scrollspy.Constructor = ScrollSpy
+ $(this._scrollElement).trigger(ScrollSpy._Event.ACTIVATE, {
+ relatedTarget: target
+ })
+}
- // SCROLLSPY NO CONFLICT
- // =====================
+/**
+ * @private
+ */
+ScrollSpy.prototype._clear = function () {
+ var activeParents = $(this._selector).parentsUntil(this._config.target, ScrollSpy._Selector.ACTIVE)
- $.fn.scrollspy.noConflict = function () {
- $.fn.scrollspy = old
- return this
+ for (var i = activeParents.length; i--;) {
+ $(activeParents[i]).removeClass(ScrollSpy._ClassName.ACTIVE)
}
+}
- // SCROLLSPY DATA-API
- // ==================
+/**
+ * ------------------------------------------------------------------------
+ * jQuery Interface + noConflict implementaiton
+ * ------------------------------------------------------------------------
+ */
- $(window).on('load.bs.scrollspy.data-api', function () {
- $('[data-spy="scroll"]').each(function () {
- var $spy = $(this)
- Plugin.call($spy, $spy.data())
- })
- })
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[ScrollSpy._NAME] = ScrollSpy._jQueryInterface
+
+
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[ScrollSpy._NAME]['Constructor'] = ScrollSpy
-}(jQuery);
+
+/**
+ * @const
+ * @type {Function}
+ */
+$.fn[ScrollSpy._NAME]['noConflict'] = function () {
+ $.fn[ScrollSpy._NAME] = ScrollSpy._JQUERY_NO_CONFLICT
+ return this
+}
+
+
+/**
+ * ------------------------------------------------------------------------
+ * Data Api implementation
+ * ------------------------------------------------------------------------
+ */
+
+$(window).on('load.bs.scrollspy.data-api', function () {
+ var scrollSpys = /** @type {Array.<Element>} */ ($.makeArray($(ScrollSpy._Selector.DATA_SPY)))
+
+ for (var i = scrollSpys.length; i--;) {
+ var $spy = $(scrollSpys[i])
+ ScrollSpy._jQueryInterface.call($spy, /** @type {Object|null} */ ($spy.data()))
+ }
+})