diff options
| author | fat <[email protected]> | 2015-01-03 13:58:44 -0800 |
|---|---|---|
| committer | fat <[email protected]> | 2015-02-11 11:29:43 -0800 |
| commit | 834220ea20ce5b7cd31edfb624a28b4bf8b29a6a (patch) | |
| tree | 19bfdadb0c140df437e7b7034e5e1f5ce58343cc /js/scrollspy.js | |
| parent | aed1cd31218113d67d2eca3296edf5d1700b19b8 (diff) | |
| download | bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.tar.xz bootstrap-834220ea20ce5b7cd31edfb624a28b4bf8b29a6a.zip | |
bootstrap onto closure
Diffstat (limited to 'js/scrollspy.js')
| -rw-r--r-- | js/scrollspy.js | 427 |
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())) + } +}) |
