From 2d91494d96f8161f1bcce6589081880b72706154 Mon Sep 17 00:00:00 2001 From: fat <fat@folders.local> Date: Mon, 11 May 2015 12:05:35 -0700 Subject: [PATCH] scrollspy es6 --- Gruntfile.js | 15 +- js/dist/scrollspy.js | 268 ++++++++++++++++++++++++++ js/dist/scrollspy.js.map | Bin 0 -> 13403 bytes js/modal.js | 337 --------------------------------- js/src/scrollspy.js | 275 +++++++++++++++++++++++++++ js/tests/index.html | 2 +- js/tests/visual/scrollspy.html | 27 +-- 7 files changed, 561 insertions(+), 363 deletions(-) create mode 100644 js/dist/scrollspy.js create mode 100644 js/dist/scrollspy.js.map delete mode 100644 js/modal.js create mode 100644 js/src/scrollspy.js diff --git a/Gruntfile.js b/Gruntfile.js index 4182d9d3c3..b3c29c3533 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -63,13 +63,14 @@ module.exports = function (grunt) { }, dist: { files: { - 'js/dist/util.js' : 'js/src/util.js', - 'js/dist/alert.js' : 'js/src/alert.js', - 'js/dist/button.js' : 'js/src/button.js', - 'js/dist/carousel.js' : 'js/src/carousel.js', - 'js/dist/collapse.js' : 'js/src/collapse.js', - 'js/dist/dropdown.js' : 'js/src/dropdown.js', - 'js/dist/modal.js' : 'js/src/modal.js' + 'js/dist/util.js' : 'js/src/util.js', + 'js/dist/alert.js' : 'js/src/alert.js', + 'js/dist/button.js' : 'js/src/button.js', + 'js/dist/carousel.js' : 'js/src/carousel.js', + 'js/dist/collapse.js' : 'js/src/collapse.js', + 'js/dist/dropdown.js' : 'js/src/dropdown.js', + 'js/dist/modal.js' : 'js/src/modal.js', + 'js/dist/scrollspy.js' : 'js/src/scrollspy.js' } } }, diff --git a/js/dist/scrollspy.js b/js/dist/scrollspy.js new file mode 100644 index 0000000000..a0d774f438 --- /dev/null +++ b/js/dist/scrollspy.js @@ -0,0 +1,268 @@ +'use strict'; + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +var ScrollSpy = (function ($) { + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + var NAME = 'scrollspy'; + var VERSION = '4.0.0'; + var DATA_KEY = 'bs.scrollspy'; + var JQUERY_NO_CONFLICT = $.fn[NAME]; + var TRANSITION_DURATION = 150; + + var Defaults = { + offset: 10 + }; + + var Event = { + ACTIVATE: 'activate.bs.scrollspy', + SCROLL: 'scroll.bs.scrollspy', + LOAD: 'load.bs.scrollspy.data-api' + }; + + var ClassName = { + DROPDOWN_MENU: 'dropdown-menu', + ACTIVE: 'active' + }; + + var Selector = { + DATA_SPY: '[data-spy="scroll"]', + ACTIVE: '.active', + LI_DROPDOWN: 'li.dropdown', + LI: 'li' + }; + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + var ScrollSpy = (function () { + function ScrollSpy(element, config) { + _classCallCheck(this, ScrollSpy); + + this._scrollElement = element.tagName === 'BODY' ? window : element; + this._config = $.extend({}, Defaults, config); + this._selector = '' + (this._config.target || '') + ' .nav li > a'; + this._offsets = []; + this._targets = []; + this._activeTarget = null; + this._scrollHeight = 0; + + $(this._scrollElement).on(Event.SCROLL, this._process.bind(this)); + + this.refresh(); + this._process(); + } + + _createClass(ScrollSpy, [{ + key: 'refresh', + + // public + + value: function refresh() { + var _this = this; + + var offsetMethod = 'offset'; + var offsetBase = 0; + + if (this._scrollElement !== this._scrollElement.window) { + offsetMethod = 'position'; + offsetBase = this._getScrollTop(); + } + + this._offsets = []; + this._targets = []; + + this._scrollHeight = this._getScrollHeight(); + + var targets = $.makeArray($(this._selector)); + + targets.map(function (element) { + var target = undefined; + var targetSelector = Util.getSelectorFromElement(element); + + 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) { + _this._offsets.push(item[0]); + _this._targets.push(item[1]); + }); + } + }, { + key: '_getScrollTop', + + // private + + value: function _getScrollTop() { + return this._scrollElement === window ? this._scrollElement.scrollY : this._scrollElement.scrollTop; + } + }, { + key: '_getScrollHeight', + value: function _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); + } + }, { + key: '_process', + value: function _process() { + 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); + } + } + + if (this._activeTarget && scrollTop < this._offsets[0]) { + this._activeTarget = null; + this._clear(); + return; + } + + for (var i = this._offsets.length; i--;) { + var isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (this._offsets[i + 1] === undefined || scrollTop < this._offsets[i + 1]); + + if (isActiveTarget) { + this._activate(this._targets[i]); + } + } + } + }, { + key: '_activate', + value: function _activate(target) { + this._activeTarget = target; + + this._clear(); + + var selector = '' + this._selector + '[data-target="' + target + '"],' + ('' + this._selector + '[href="' + target + '"]'); + + // todo (fat): getting all the raw li's up the tree is not great. + var parentListItems = $(selector).parents(Selector.LI); + + for (var i = parentListItems.length; i--;) { + $(parentListItems[i]).addClass(ClassName.ACTIVE); + + var itemParent = parentListItems[i].parentNode; + + if (itemParent && $(itemParent).hasClass(ClassName.DROPDOWN_MENU)) { + var closestDropdown = $(itemParent).closest(Selector.LI_DROPDOWN)[0]; + $(closestDropdown).addClass(ClassName.ACTIVE); + } + } + + $(this._scrollElement).trigger(Event.ACTIVATE, { + relatedTarget: target + }); + } + }, { + key: '_clear', + value: function _clear() { + var activeParents = $(this._selector).parentsUntil(this._config.target, Selector.ACTIVE); + + for (var i = activeParents.length; i--;) { + $(activeParents[i]).removeClass(ClassName.ACTIVE); + } + } + }], [{ + key: 'VERSION', + + // getters + + get: function () { + return VERSION; + } + }, { + key: 'Default', + get: function () { + return Default; + } + }, { + key: '_jQueryInterface', + + // static + + value: function _jQueryInterface(config) { + return this.each(function () { + var data = $(this).data(DATA_KEY); + var _config = typeof config === 'object' && config || null; + + if (!data) { + data = new ScrollSpy(this, _config); + $(this).data(DATA_KEY, data); + } + + if (typeof config === 'string') { + data[config](); + } + }); + } + }]); + + return ScrollSpy; + })(); + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(window).on(Event.LOAD, function () { + var scrollSpys = $.makeArray($(Selector.DATA_SPY)); + + for (var i = scrollSpys.length; i--;) { + var $spy = $(scrollSpys[i]); + ScrollSpy._jQueryInterface.call($spy, $spy.data()); + } + }); + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = ScrollSpy._jQueryInterface; + $.fn[NAME].Constructor = ScrollSpy; + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT; + return ScrollSpy._jQueryInterface; + }; + + return ScrollSpy; +})(jQuery); +//# sourceMappingURL=scrollspy.js.map \ No newline at end of file diff --git a/js/dist/scrollspy.js.map b/js/dist/scrollspy.js.map new file mode 100644 index 0000000000000000000000000000000000000000..ba686262fc673f2f9f97afea8a501eb0480ca028 GIT binary patch literal 13403 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 literal 0 HcmV?d00001 diff --git a/js/modal.js b/js/modal.js deleted file mode 100644 index 0426561af6..0000000000 --- a/js/modal.js +++ /dev/null @@ -1,337 +0,0 @@ -/* ======================================================================== - * Bootstrap: modal.js v3.3.4 - * http://getbootstrap.com/javascript/#modals - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // MODAL CLASS DEFINITION - // ====================== - - var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$dialog = this.$element.find('.modal-dialog') - this.$backdrop = null - this.isShown = null - this.originalBodyPad = null - this.scrollbarWidth = 0 - this.ignoreBackdropClick = false - - 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.4' - - 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 }) - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - this.isShown = true - - this.checkScrollbar() - this.setScrollbar() - this.$body.addClass('modal-open') - - this.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) // don't move modals dom position - } - - that.$element - .show() - .scrollTop(0) - - that.adjustDialog() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element.addClass('in') - - that.enforceFocus() - - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) - - transition ? - that.$dialog // wait for modal to slide in - .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') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - this.escape() - this.resize() - - $(document).off('focusin.bs.modal') - - this.$element - .removeClass('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') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { - if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { - this.$element.trigger('focus') - } - }, this)) - } - - 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() - }, 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') - that.resetAdjustments() - that.resetScrollbar() - that.$element.trigger('hidden.bs.modal') - }) - } - - 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() - : this.hide() - }, this)) - - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow - - 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() - } - } - - // these following methods are used to handle overflowing modals - - 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 : '', - 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) { // workaround for missing window.innerWidth in IE8 - var documentElementRect = document.documentElement.getBoundingClientRect() - fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) - } - this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth - 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) - } - - 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 PLUGIN DEFINITION - // ======================= - - 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))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) - } - - var old = $.fn.modal - - $.fn.modal = Plugin - $.fn.modal.Constructor = Modal - - - // MODAL NO CONFLICT - // ================= - - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } - - - // MODAL DATA-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]+$)/, ''))) // strip for ie7 - 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 // only register focus restorer if modal will actually get shown - $target.one('hidden.bs.modal', function () { - $this.is(':visible') && $this.trigger('focus') - }) - }) - Plugin.call($target, option, this) - }) - -}(jQuery); diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js new file mode 100644 index 0000000000..985da708d0 --- /dev/null +++ b/js/src/scrollspy.js @@ -0,0 +1,275 @@ +import Util from './util' + + +/** + * -------------------------------------------------------------------------- + * Bootstrap (v4.0.0): scrollspy.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * -------------------------------------------------------------------------- + */ + +const ScrollSpy = (($) => { + + + /** + * ------------------------------------------------------------------------ + * Constants + * ------------------------------------------------------------------------ + */ + + const NAME = 'scrollspy' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.scrollspy' + const JQUERY_NO_CONFLICT = $.fn[NAME] + const TRANSITION_DURATION = 150 + + const Defaults = { + offset : 10 + } + + const Event = { + ACTIVATE : 'activate.bs.scrollspy', + SCROLL : 'scroll.bs.scrollspy', + LOAD : 'load.bs.scrollspy.data-api' + } + + const ClassName = { + DROPDOWN_MENU : 'dropdown-menu', + ACTIVE : 'active' + } + + const Selector = { + DATA_SPY : '[data-spy="scroll"]', + ACTIVE : '.active', + LI_DROPDOWN : 'li.dropdown', + LI : 'li' + } + + + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ + + class ScrollSpy { + + constructor(element, config) { + this._scrollElement = element.tagName === 'BODY' ? window : element + this._config = $.extend({}, Defaults, config) + this._selector = `${this._config.target || ''} .nav li > a` + this._offsets = [] + this._targets = [] + this._activeTarget = null + this._scrollHeight = 0 + + $(this._scrollElement).on(Event.SCROLL, this._process.bind(this)) + + this.refresh() + this._process() + } + + + // getters + + static get VERSION() { + return VERSION + } + + static get Default() { + return Default + } + + + // public + + refresh() { + let offsetMethod = 'offset' + let offsetBase = 0 + + if (this._scrollElement !== this._scrollElement.window) { + offsetMethod = 'position' + offsetBase = this._getScrollTop() + } + + this._offsets = [] + this._targets = [] + + this._scrollHeight = this._getScrollHeight() + + let targets = $.makeArray($(this._selector)) + + targets + .map((element) => { + let target + let targetSelector = Util.getSelectorFromElement(element) + + 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((item) => item) + .sort((a, b) => a[0] - b[0]) + .forEach((item) => { + this._offsets.push(item[0]) + this._targets.push(item[1]) + }) + } + + + // private + + _getScrollTop() { + return this._scrollElement === window ? + this._scrollElement.scrollY : this._scrollElement.scrollTop + } + + _getScrollHeight() { + return this._scrollElement.scrollHeight || Math.max( + document.body.scrollHeight, + document.documentElement.scrollHeight + ) + } + + _process() { + let scrollTop = this._getScrollTop() + this._config.offset + let scrollHeight = this._getScrollHeight() + let maxScroll = this._config.offset + + scrollHeight + - this._scrollElement.offsetHeight + + if (this._scrollHeight !== scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + let target = this._targets[this._targets.length - 1] + + if (this._activeTarget !== target) { + this._activate(target) + } + } + + if (this._activeTarget && scrollTop < this._offsets[0]) { + this._activeTarget = null + this._clear() + return + } + + for (let i = this._offsets.length; i--;) { + let isActiveTarget = this._activeTarget !== this._targets[i] + && scrollTop >= this._offsets[i] + && (this._offsets[i + 1] === undefined || + scrollTop < this._offsets[i + 1]) + + if (isActiveTarget) { + this._activate(this._targets[i]) + } + } + } + + _activate(target) { + this._activeTarget = target + + this._clear() + + let selector = + `${this._selector}[data-target="${target}"],` + + `${this._selector}[href="${target}"]` + + // todo (fat): getting all the raw li's up the tree is not great. + let parentListItems = $(selector).parents(Selector.LI) + + for (let i = parentListItems.length; i--;) { + $(parentListItems[i]).addClass(ClassName.ACTIVE) + + let itemParent = parentListItems[i].parentNode + + if (itemParent && $(itemParent).hasClass(ClassName.DROPDOWN_MENU)) { + let closestDropdown = $(itemParent) + .closest(Selector.LI_DROPDOWN)[0] + $(closestDropdown).addClass(ClassName.ACTIVE) + } + } + + $(this._scrollElement).trigger(Event.ACTIVATE, { + relatedTarget: target + }) + } + + _clear() { + let activeParents = $(this._selector).parentsUntil( + this._config.target, + Selector.ACTIVE + ) + + for (let i = activeParents.length; i--;) { + $(activeParents[i]).removeClass(ClassName.ACTIVE) + } + } + + + // static + + static _jQueryInterface(config) { + return this.each(function () { + let data = $(this).data(DATA_KEY) + let _config = typeof config === 'object' && config || null + + if (!data) { + data = new ScrollSpy(this, _config) + $(this).data(DATA_KEY, data) + } + + if (typeof config === 'string') { + data[config]() + } + }) + } + + + } + + + /** + * ------------------------------------------------------------------------ + * Data Api implementation + * ------------------------------------------------------------------------ + */ + + $(window).on(Event.LOAD, function () { + let scrollSpys = $.makeArray($(Selector.DATA_SPY)) + + for (let i = scrollSpys.length; i--;) { + let $spy = $(scrollSpys[i]) + ScrollSpy._jQueryInterface.call($spy, $spy.data()) + } + }) + + + /** + * ------------------------------------------------------------------------ + * jQuery + * ------------------------------------------------------------------------ + */ + + $.fn[NAME] = ScrollSpy._jQueryInterface + $.fn[NAME].Constructor = ScrollSpy + $.fn[NAME].noConflict = function () { + $.fn[NAME] = JQUERY_NO_CONFLICT + return ScrollSpy._jQueryInterface + } + + return ScrollSpy + +})(jQuery) + +export default ScrollSpy diff --git a/js/tests/index.html b/js/tests/index.html index 65c75acfee..0262eec2a9 100644 --- a/js/tests/index.html +++ b/js/tests/index.html @@ -137,9 +137,9 @@ <script src="../../js/dist/collapse.js"></script> <script src="../../js/dist/dropdown.js"></script> <script src="../../js/dist/modal.js"></script> + <script src="../../js/dist/scrollspy.js"></script> <!-- Old Plugin sources --> - <script src="../../js/scrollspy.js"></script> <script src="../../js/tab.js"></script> <script src="../../js/tooltip.js"></script> <script src="../../js/popover.js"></script> diff --git a/js/tests/visual/scrollspy.html b/js/tests/visual/scrollspy.html index 5952b7e593..9958f90c08 100644 --- a/js/tests/visual/scrollspy.html +++ b/js/tests/visual/scrollspy.html @@ -24,21 +24,12 @@ <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <div class="container-fluid"> - <div class="navbar-header"> - <button class="navbar-toggle collapsed" type="button" data-toggle="collapse" data-target=".js-navbar-scrollspy"> - <span class="sr-only">Toggle navigation</span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - <span class="icon-bar"></span> - </button> - <a class="navbar-brand" href="#">Project Name</a> - </div> - <div class="collapse navbar-collapse js-navbar-scrollspy"> + <div class="js-navbar-scrollspy"> <ul class="nav navbar-nav"> - <li class=""><a href="#fat">@fat</a></li> - <li class=""><a href="#mdo">@mdo</a></li> - <li class="dropdown"> - <a href="#" class="dropdown-toggle" data-toggle="dropdown">Dropdown <b class="caret"></b></a> + <li class="nav-item active"><a class="nav-link" href="#fat">@fat</a></li> + <li class="nav-item"><a class="nav-link" href="#mdo">@mdo</a></li> + <li class="dropdown nav-item"> + <a href="#" class="dropdown-toggle nav-link" data-toggle="dropdown">Dropdown <b class="caret"></b></a> <ul class="dropdown-menu" role="menu"> <li class=""><a href="#one" tabindex="-1">one</a></li> <li><a href="#two" tabindex="-1">two</a></li> @@ -100,10 +91,10 @@ <!-- JavaScript Includes --> <script src="../vendor/jquery.min.js"></script> -<script src="../../transition.js"></script> -<script src="../../scrollspy.js"></script> -<script src="../../dropdown.js"></script> -<script src="../../collapse.js"></script> +<script src="../../dist/util.js"></script> +<script src="../../dist/scrollspy.js"></script> +<script src="../../dist/dropdown.js"></script> +<script src="../../dist/collapse.js"></script> </body> </html> -- GitLab