From 2d91494d96f8161f1bcce6589081880b72706154 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 12:05:35 -0700 Subject: scrollspy es6 --- js/src/scrollspy.js | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 js/src/scrollspy.js (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js new file mode 100644 index 000000000..985da708d --- /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 -- cgit v1.2.3 From 8eee78ca15f51dc7e7d514497078bfd7c012ac21 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 12:29:06 -0700 Subject: tab es6 --- js/src/scrollspy.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 985da708d..b66f7bb88 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -17,11 +17,10 @@ const ScrollSpy = (($) => { * ------------------------------------------------------------------------ */ - const NAME = 'scrollspy' - const VERSION = '4.0.0' - const DATA_KEY = 'bs.scrollspy' - const JQUERY_NO_CONFLICT = $.fn[NAME] - const TRANSITION_DURATION = 150 + const NAME = 'scrollspy' + const VERSION = '4.0.0' + const DATA_KEY = 'bs.scrollspy' + const JQUERY_NO_CONFLICT = $.fn[NAME] const Defaults = { offset : 10 -- cgit v1.2.3 From 3452e8dc8336c7a4151bcccdb9d3d4202f06f294 Mon Sep 17 00:00:00 2001 From: fat Date: Mon, 11 May 2015 23:32:37 -0700 Subject: rewritten tooltip + tether integration and death to our positioner jank --- js/src/scrollspy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index b66f7bb88..0ab8804c6 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -22,7 +22,7 @@ const ScrollSpy = (($) => { const DATA_KEY = 'bs.scrollspy' const JQUERY_NO_CONFLICT = $.fn[NAME] - const Defaults = { + const Default = { offset : 10 } -- cgit v1.2.3 From a58febf71a5eac2161ce2db08c7d723755ed1163 Mon Sep 17 00:00:00 2001 From: fat Date: Tue, 12 May 2015 14:28:11 -0700 Subject: popover passing as well --- js/src/scrollspy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 0ab8804c6..e41a3ae12 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -55,7 +55,7 @@ const ScrollSpy = (($) => { constructor(element, config) { this._scrollElement = element.tagName === 'BODY' ? window : element - this._config = $.extend({}, Defaults, config) + this._config = $.extend({}, Default, config) this._selector = `${this._config.target || ''} .nav li > a` this._offsets = [] this._targets = [] -- cgit v1.2.3 From ab1578465aee4a776412b48f16bfefca79381919 Mon Sep 17 00:00:00 2001 From: fat Date: Tue, 12 May 2015 16:52:54 -0700 Subject: grunt test-js, grunt dist-js now working --- js/src/scrollspy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index e41a3ae12..763d133e8 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -62,7 +62,7 @@ const ScrollSpy = (($) => { this._activeTarget = null this._scrollHeight = 0 - $(this._scrollElement).on(Event.SCROLL, this._process.bind(this)) + $(this._scrollElement).on(Event.SCROLL, $.proxy(this._process, this)) this.refresh() this._process() -- cgit v1.2.3 From f8b2569ec8956a1f4d09fe6fc9865bd200ecde43 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 12:48:34 -0700 Subject: implement global dispose method --- js/src/scrollspy.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 763d133e8..d1be3a45f 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -20,6 +20,8 @@ const ScrollSpy = (($) => { const NAME = 'scrollspy' const VERSION = '4.0.0' const DATA_KEY = 'bs.scrollspy' + const EVENT_KEY = `.${DATA_KEY}` + const DATA_API_KEY = '.data-api' const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { @@ -27,9 +29,9 @@ const ScrollSpy = (($) => { } const Event = { - ACTIVATE : 'activate.bs.scrollspy', - SCROLL : 'scroll.bs.scrollspy', - LOAD : 'load.bs.scrollspy.data-api' + ACTIVATE : `activate${EVENT_KEY}`, + SCROLL : `scroll${EVENT_KEY}`, + LOAD_DATA_API : `load${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -54,6 +56,7 @@ const ScrollSpy = (($) => { class ScrollSpy { constructor(element, config) { + this._element = element this._scrollElement = element.tagName === 'BODY' ? window : element this._config = $.extend({}, Default, config) this._selector = `${this._config.target || ''} .nav li > a` @@ -123,6 +126,20 @@ const ScrollSpy = (($) => { }) } + dispose() { + $.removeData(this._element, DATA_KEY) + $(this._scrollElement).off(EVENT_KEY) + + this._element = null + this._scrollElement = null + this._config = null + this._selector = null + this._offsets = null + this._targets = null + this._activeTarget = null + this._scrollHeight = null + } + // private @@ -244,7 +261,7 @@ const ScrollSpy = (($) => { * ------------------------------------------------------------------------ */ - $(window).on(Event.LOAD, function () { + $(window).on(Event.LOAD_DATA_API, function () { let scrollSpys = $.makeArray($(Selector.DATA_SPY)) for (let i = scrollSpys.length; i--;) { -- cgit v1.2.3 From da495ee24c239ef9c9c154670b1a641745ac147f Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 12:55:11 -0700 Subject: address https://github.com/twbs/bootstrap/pull/16135 --- js/src/scrollspy.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index d1be3a45f..3f4a145a1 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -25,7 +25,8 @@ const ScrollSpy = (($) => { const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { - offset : 10 + offset : 10, + method : 'auto' } const Event = { @@ -46,6 +47,11 @@ const ScrollSpy = (($) => { LI : 'li' } + const OffsetMethod = { + OFFSET : 'offset', + POSITION : 'position' + } + /** * ------------------------------------------------------------------------ @@ -86,13 +92,14 @@ const ScrollSpy = (($) => { // public refresh() { - let offsetMethod = 'offset' - let offsetBase = 0 + let autoMethod = this._scrollElement !== this._scrollElement.window ? + OffsetMethod.POSITION : OffsetMethod.OFFSET - if (this._scrollElement !== this._scrollElement.window) { - offsetMethod = 'position' - offsetBase = this._getScrollTop() - } + let offsetMethod = this._config.method === 'auto' ? + autoMethod : this._config.method + + let offsetBase = offsetMethod === OffsetMethod.POSITION ? + this._getScrollTop() : 0 this._offsets = [] this._targets = [] -- cgit v1.2.3 From b0d142334f0d15e63577b28e2d7045c2199dcd6a Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 13:43:56 -0700 Subject: fix #15301 --- js/src/scrollspy.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 3f4a145a1..bb639f91b 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -26,7 +26,8 @@ const ScrollSpy = (($) => { const Default = { offset : 10, - method : 'auto' + method : 'auto', + target : '' } const Event = { @@ -43,8 +44,9 @@ const ScrollSpy = (($) => { const Selector = { DATA_SPY : '[data-spy="scroll"]', ACTIVE : '.active', + LI : 'li', LI_DROPDOWN : 'li.dropdown', - LI : 'li' + NAV_ANCHORS : '.nav li > a' } const OffsetMethod = { @@ -64,8 +66,8 @@ const ScrollSpy = (($) => { constructor(element, config) { this._element = element this._scrollElement = element.tagName === 'BODY' ? window : element - this._config = $.extend({}, Default, config) - this._selector = `${this._config.target || ''} .nav li > a` + this._config = this._getConfig(config) + this._selector = `${this._config.target} ${Selector.NAV_ANCHORS}` this._offsets = [] this._targets = [] this._activeTarget = null @@ -150,6 +152,21 @@ const ScrollSpy = (($) => { // private + _getConfig(config) { + config = $.extend({}, Default, config) + + if (typeof config.target !== 'string') { + let id = $(config.target).attr('id') + if (!id) { + id = Util.getUID(NAME) + $(config.target).attr('id', id) + } + config.target = `#${id}` + } + + return config + } + _getScrollTop() { return this._scrollElement === window ? this._scrollElement.scrollY : this._scrollElement.scrollTop -- cgit v1.2.3 From eaab1def7af7d7e1ab32ff69d043b46e2815ca22 Mon Sep 17 00:00:00 2001 From: fat Date: Wed, 13 May 2015 14:46:50 -0700 Subject: add simple type checker implementation --- js/src/scrollspy.js | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'js/src/scrollspy.js') diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index bb639f91b..a407511f6 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -30,6 +30,12 @@ const ScrollSpy = (($) => { target : '' } + const DefaultType = { + offset : 'number', + method : 'string', + target : '(string|element)' + } + const Event = { ACTIVATE : `activate${EVENT_KEY}`, SCROLL : `scroll${EVENT_KEY}`, @@ -164,6 +170,8 @@ const ScrollSpy = (($) => { config.target = `#${id}` } + Util.typeCheckConfig(NAME, config, DefaultType) + return config } -- cgit v1.2.3