From 0263d1742ce8ad25f0f2de30beebae69b2f55f10 Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Mon, 25 Sep 2017 09:09:01 +0200 Subject: [PATCH] rewritten scrollspy without jquery --- js/src/dom/manipulator.js | 16 ++++++ js/src/dom/selectorEngine.js | 42 ++++++++++++++-- js/src/scrollspy.js | 91 ++++++++++++++++++++-------------- js/tests/unit/scrollspy.js | 14 +++--- js/tests/visual/scrollspy.html | 3 ++ 5 files changed, 117 insertions(+), 49 deletions(-) diff --git a/js/src/dom/manipulator.js b/js/src/dom/manipulator.js index 215837bf67..201902b77e 100644 --- a/js/src/dom/manipulator.js +++ b/js/src/dom/manipulator.js @@ -40,6 +40,22 @@ const Manipulator = { element.removeAttribute(`data-${key.replace(/[A-Z]/g, (chr) => `-${chr.toLowerCase()}`)}`) }, + offset(element) { + const rect = element.getBoundingClientRect() + + return { + top: rect.top + document.body.scrollTop, + left: rect.left + document.body.scrollLeft + } + }, + + position(element) { + return { + top: element.offsetTop, + left: element.offsetLeft + } + }, + toggleClass(element, className) { if (typeof element === 'undefined' || element === null) { return diff --git a/js/src/dom/selectorEngine.js b/js/src/dom/selectorEngine.js index a3e88ad6eb..e515164458 100644 --- a/js/src/dom/selectorEngine.js +++ b/js/src/dom/selectorEngine.js @@ -111,10 +111,6 @@ const SelectorEngine = (() => { return null } - if (selector.indexOf('#') === 0) { - return SelectorEngine.findOne(selector, element) - } - return findFn.call(element, selector) }, @@ -135,8 +131,46 @@ const SelectorEngine = (() => { return children.filter((child) => this.matches(child, selector)) }, + parents(element, selector) { + if (typeof selector !== 'string') { + return null + } + + const parents = [] + + let ancestor = element.parentNode + while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE) { + if (fnMatches.call(ancestor, selector)) { + parents.push(ancestor) + } + + ancestor = ancestor.parentNode + } + + return parents + }, + closest(element, selector) { return fnClosest(element, selector) + }, + + prev(element, selector) { + if (typeof selector !== 'string') { + return null + } + + const siblings = [] + + let previous = element.previousSibling + while (previous) { + if (fnMatches.call(previous, selector)) { + siblings.push(previous) + } + + previous = previous.previousSibling + } + + return siblings } } })() diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index e8cd6bf98c..ea6d528157 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -5,7 +5,10 @@ * -------------------------------------------------------------------------- */ -import $ from 'jquery' +import Data from './dom/data' +import EventHandler from './dom/eventHandler' +import Manipulator from './dom/manipulator' +import SelectorEngine from './dom/selectorEngine' import Util from './util' /** @@ -19,7 +22,6 @@ const VERSION = '4.3.1' const DATA_KEY = 'bs.scrollspy' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' -const JQUERY_NO_CONFLICT = $.fn[NAME] const Default = { offset : 10, @@ -81,10 +83,12 @@ class ScrollSpy { this._activeTarget = null this._scrollHeight = 0 - $(this._scrollElement).on(Event.SCROLL, (event) => this._process(event)) + EventHandler.on(this._scrollElement, Event.SCROLL, (event) => this._process(event)) this.refresh() this._process() + + Data.setData(element, DATA_KEY, this) } // Getters @@ -114,7 +118,7 @@ class ScrollSpy { this._scrollHeight = this._getScrollHeight() - const targets = [].slice.call(document.querySelectorAll(this._selector)) + const targets = Util.makeArray(document.querySelectorAll(this._selector)) targets .map((element) => { @@ -130,7 +134,7 @@ class ScrollSpy { if (targetBCR.width || targetBCR.height) { // TODO (fat): remove sketch reliance on jQuery position/offset return [ - $(target)[offsetMethod]().top + offsetBase, + Manipulator[offsetMethod](target).top + offsetBase, targetSelector ] } @@ -146,8 +150,8 @@ class ScrollSpy { } dispose() { - $.removeData(this._element, DATA_KEY) - $(this._scrollElement).off(EVENT_KEY) + Data.removeData(this._element, DATA_KEY) + EventHandler.off(this._scrollElement, EVENT_KEY) this._element = null this._scrollElement = null @@ -168,10 +172,10 @@ class ScrollSpy { } if (typeof config.target !== 'string') { - let id = $(config.target).attr('id') + let id = config.target.id if (!id) { id = Util.getUID(NAME) - $(config.target).attr('id', id) + config.target.id = id } config.target = `#${id}` } @@ -242,32 +246,45 @@ class ScrollSpy { this._clear() - const queries = this._selector - .split(',') + const queries = this._selector.split(',') .map((selector) => `${selector}[data-target="${target}"],${selector}[href="${target}"]`) - const $link = $([].slice.call(document.querySelectorAll(queries.join(',')))) + const link = SelectorEngine.findOne(queries.join(',')) - if ($link.hasClass(ClassName.DROPDOWN_ITEM)) { - $link.closest(Selector.DROPDOWN).find(Selector.DROPDOWN_TOGGLE).addClass(ClassName.ACTIVE) - $link.addClass(ClassName.ACTIVE) + if (link.classList.contains(ClassName.DROPDOWN_ITEM)) { + SelectorEngine + .findOne(Selector.DROPDOWN_TOGGLE, SelectorEngine.closest(link, Selector.DROPDOWN)) + .classList.add(ClassName.ACTIVE) + + link.classList.add(ClassName.ACTIVE) } else { // Set triggered link as active - $link.addClass(ClassName.ACTIVE) - // Set triggered links parents as active - // With both