diff options
author | GeoSot <geo.sotis@gmail.com> | 2022-03-16 12:58:58 +0300 |
---|---|---|
committer | GeoSot <geo.sotis@gmail.com> | 2022-10-08 00:20:25 +0300 |
commit | 8847c8156e324412280d54a5d86af466f1cc554a (patch) | |
tree | f89468a6153a074668b9c612d1cef338aafe75ba | |
parent | 4cb046a6b8b37a0f328fa5b86fbd573ca3f0dc33 (diff) |
refactor: Move `getElementFromSelector` & `getSelectorFromElement`gs/moving-selector-helpers
Move `getElementFromSelector` & getSelectorFromElement` inside selector-engine.js, in order to use SelectorEngine methods, avoiding raw querySelector usage
Feature: add `getMultipleElementsFromSelector` helper
-rw-r--r-- | js/src/carousel.js | 3 | ||||
-rw-r--r-- | js/src/collapse.js | 16 | ||||
-rw-r--r-- | js/src/dom/selector-engine.js | 54 | ||||
-rw-r--r-- | js/src/dropdown.js | 2 | ||||
-rw-r--r-- | js/src/modal.js | 4 | ||||
-rw-r--r-- | js/src/offcanvas.js | 3 | ||||
-rw-r--r-- | js/src/scrollspy.js | 2 | ||||
-rw-r--r-- | js/src/tab.js | 4 | ||||
-rw-r--r-- | js/src/util/component-functions.js | 3 | ||||
-rw-r--r-- | js/src/util/focustrap.js | 2 | ||||
-rw-r--r-- | js/src/util/index.js | 43 | ||||
-rw-r--r-- | js/src/util/scrollbar.js | 2 | ||||
-rw-r--r-- | js/src/util/template-factory.js | 2 | ||||
-rw-r--r-- | js/tests/unit/dom/selector-engine.spec.js | 165 | ||||
-rw-r--r-- | js/tests/unit/util/focustrap.spec.js | 2 | ||||
-rw-r--r-- | js/tests/unit/util/index.spec.js | 113 |
16 files changed, 235 insertions, 185 deletions
diff --git a/js/src/carousel.js b/js/src/carousel.js index 7e89e1614c..49527200d5 100644 --- a/js/src/carousel.js +++ b/js/src/carousel.js @@ -7,7 +7,6 @@ import { defineJQueryPlugin, - getElementFromSelector, getNextActiveElement, isRTL, isVisible, @@ -16,7 +15,7 @@ import { } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' +import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' import Swipe from './util/swipe' import BaseComponent from './base-component' diff --git a/js/src/collapse.js b/js/src/collapse.js index df2bbc6473..4999257f8f 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -5,15 +5,14 @@ * -------------------------------------------------------------------------- */ +import { defineJQueryPlugin, getElement, reflow } from './util/index' +import EventHandler from './dom/event-handler' import { - defineJQueryPlugin, - getElement, getElementFromSelector, + getMultipleElementsFromSelector, getSelectorFromElement, - reflow -} from './util/index' -import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' + SelectorEngine +} from './dom/selector-engine' import BaseComponent from './base-component' /** @@ -285,10 +284,7 @@ EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function ( event.preventDefault() } - const selector = getSelectorFromElement(this) - const selectorElements = SelectorEngine.find(selector) - - for (const element of selectorElements) { + for (const element of getMultipleElementsFromSelector(this)) { Collapse.getOrCreateInstance(element, { toggle: false }).toggle() } }) diff --git a/js/src/dom/selector-engine.js b/js/src/dom/selector-engine.js index 28f23ff8ff..69eb79aa21 100644 --- a/js/src/dom/selector-engine.js +++ b/js/src/dom/selector-engine.js @@ -80,4 +80,56 @@ const SelectorEngine = { } } -export default SelectorEngine +const getSelector = element => { + let selector = element.getAttribute('data-bs-target') + + if (!selector || selector === '#') { + let hrefAttribute = element.getAttribute('href') + + // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 + if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { + return null + } + + // Just in case some CMS puts out a full URL with the anchor appended + if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { + hrefAttribute = `#${hrefAttribute.split('#')[1]}` + } + + selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null + } + + return selector +} + +const getSelectorFromElement = element => { + const selector = getSelector(element) + + if (selector) { + return SelectorEngine.findOne(selector) ? selector : null + } + + return null +} + +const getElementFromSelector = element => { + const selector = getSelector(element) + + return selector ? SelectorEngine.findOne(selector) : null +} + +const getMultipleElementsFromSelector = element => { + const selector = getSelector(element) + + return selector ? SelectorEngine.find(selector) : [] +} + +export { + SelectorEngine, + getElementFromSelector, + getMultipleElementsFromSelector, + getSelectorFromElement +} diff --git a/js/src/dropdown.js b/js/src/dropdown.js index d37886d898..1f4f2c2bda 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -19,7 +19,7 @@ import { } from './util/index' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' +import { SelectorEngine } from './dom/selector-engine' import BaseComponent from './base-component' /** diff --git a/js/src/modal.js b/js/src/modal.js index c2c5c19e96..22ae926b29 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -5,9 +5,9 @@ * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index' +import { defineJQueryPlugin, isRTL, isVisible, reflow } from './util/index' import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' +import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' import ScrollBarHelper from './util/scrollbar' import BaseComponent from './base-component' import Backdrop from './util/backdrop' diff --git a/js/src/offcanvas.js b/js/src/offcanvas.js index dc3e910752..436f441c91 100644 --- a/js/src/offcanvas.js +++ b/js/src/offcanvas.js @@ -7,14 +7,13 @@ import { defineJQueryPlugin, - getElementFromSelector, isDisabled, isVisible } from './util/index' import ScrollBarHelper from './util/scrollbar' import EventHandler from './dom/event-handler' import BaseComponent from './base-component' -import SelectorEngine from './dom/selector-engine' +import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' import Backdrop from './util/backdrop' import FocusTrap from './util/focustrap' import { enableDismissTrigger } from './util/component-functions' diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index a73bba840b..ae70ae8bc4 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -7,7 +7,7 @@ import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' +import { SelectorEngine } from './dom/selector-engine' import BaseComponent from './base-component' /** diff --git a/js/src/tab.js b/js/src/tab.js index a78c27538a..6d53016f07 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -5,9 +5,9 @@ * -------------------------------------------------------------------------- */ -import { defineJQueryPlugin, getElementFromSelector, getNextActiveElement, isDisabled } from './util/index' +import { defineJQueryPlugin, getNextActiveElement, isDisabled } from './util/index' import EventHandler from './dom/event-handler' -import SelectorEngine from './dom/selector-engine' +import { getElementFromSelector, SelectorEngine } from './dom/selector-engine' import BaseComponent from './base-component' /** diff --git a/js/src/util/component-functions.js b/js/src/util/component-functions.js index 798366b079..796b1bfa6c 100644 --- a/js/src/util/component-functions.js +++ b/js/src/util/component-functions.js @@ -6,7 +6,8 @@ */ import EventHandler from '../dom/event-handler' -import { getElementFromSelector, isDisabled } from './index' +import { isDisabled } from './index' +import { getElementFromSelector } from '../dom/selector-engine' const enableDismissTrigger = (component, method = 'hide') => { const clickEvent = `click.dismiss${component.EVENT_KEY}` diff --git a/js/src/util/focustrap.js b/js/src/util/focustrap.js index 01ac766837..5924639d22 100644 --- a/js/src/util/focustrap.js +++ b/js/src/util/focustrap.js @@ -6,7 +6,7 @@ */ import EventHandler from '../dom/event-handler' -import SelectorEngine from '../dom/selector-engine' +import { SelectorEngine } from '../dom/selector-engine' import Config from './config' /** diff --git a/js/src/util/index.js b/js/src/util/index.js index ad99f85ed9..b92eddba25 100644 --- a/js/src/util/index.js +++ b/js/src/util/index.js @@ -30,47 +30,6 @@ const getUID = prefix => { return prefix } -const getSelector = element => { - let selector = element.getAttribute('data-bs-target') - - if (!selector || selector === '#') { - let hrefAttribute = element.getAttribute('href') - - // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - if (!hrefAttribute || (!hrefAttribute.includes('#') && !hrefAttribute.startsWith('.'))) { - return null - } - - // Just in case some CMS puts out a full URL with the anchor appended - if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) { - hrefAttribute = `#${hrefAttribute.split('#')[1]}` - } - - selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null - } - - return selector -} - -const getSelectorFromElement = element => { - const selector = getSelector(element) - - if (selector) { - return document.querySelector(selector) ? selector : null - } - - return null -} - -const getElementFromSelector = element => { - const selector = getSelector(element) - - return selector ? document.querySelector(selector) : null -} - const getTransitionDurationFromElement = element => { if (!element) { return 0 @@ -316,10 +275,8 @@ export { executeAfterTransition, findShadowRoot, getElement, - getElementFromSelector, getjQuery, getNextActiveElement, - getSelectorFromElement, getTransitionDurationFromElement, getUID, isDisabled, diff --git a/js/src/util/scrollbar.js b/js/src/util/scrollbar.js index 421426d41a..d531b769d6 100644 --- a/js/src/util/scrollbar.js +++ b/js/src/util/scrollbar.js @@ -5,7 +5,7 @@ * -------------------------------------------------------------------------- */ -import SelectorEngine from '../dom/selector-engine' +import { SelectorEngine } from '../dom/selector-engine' import Manipulator from '../dom/manipulator' import { isElement } from './index' diff --git a/js/src/util/template-factory.js b/js/src/util/template-factory.js index 16ec6c28d2..1de03473cf 100644 --- a/js/src/util/template-factory.js +++ b/js/src/util/template-factory.js @@ -7,7 +7,7 @@ import { DefaultAllowlist, sanitizeHtml } from './sanitizer' import { execute, getElement, isElement } from '../util/index' -import SelectorEngine from '../dom/selector-engine' +import { SelectorEngine } from '../dom/selector-engine' import Config from './config' /** diff --git a/js/tests/unit/dom/selector-engine.spec.js b/js/tests/unit/dom/selector-engine.spec.js index 0245896c68..eb485c1356 100644 --- a/js/tests/unit/dom/selector-engine.spec.js +++ b/js/tests/unit/dom/selector-engine.spec.js @@ -1,5 +1,10 @@ -import SelectorEngine from '../../../src/dom/selector-engine' -import { getFixture, clearFixture } from '../../helpers/fixture' +import { + getElementFromSelector, + getMultipleElementsFromSelector, + getSelectorFromElement, + SelectorEngine +} from '../../../src/dom/selector-engine' +import { clearFixture, getFixture } from '../../helpers/fixture' describe('SelectorEngine', () => { let fixtureEl @@ -232,5 +237,159 @@ describe('SelectorEngine', () => { expect(SelectorEngine.focusableChildren(fixtureEl)).toEqual(expectedElements) }) }) -}) + describe('getSelectorFromElement', () => { + it('should get selector from data-bs-target', () => { + fixtureEl.innerHTML = [ + '<div id="test" data-bs-target=".target"></div>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toEqual('.target') + }) + + it('should get selector from href if no data-bs-target set', () => { + fixtureEl.innerHTML = [ + '<a id="test" href=".target"></a>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toEqual('.target') + }) + + it('should get selector from href if data-bs-target equal to #', () => { + fixtureEl.innerHTML = [ + '<a id="test" data-bs-target="#" href=".target"></a>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toEqual('.target') + }) + + it('should return null if a selector from a href is a url without an anchor', () => { + fixtureEl.innerHTML = [ + '<a id="test" data-bs-target="#" href="foo/bar.html"></a>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toBeNull() + }) + + it('should return the anchor if a selector from a href is a url', () => { + fixtureEl.innerHTML = [ + '<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>', + '<div id="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toEqual('#target') + }) + + it('should return null if selector not found', () => { + fixtureEl.innerHTML = '<a id="test" href=".target"></a>' + + const testEl = fixtureEl.querySelector('#test') + + expect(getSelectorFromElement(testEl)).toBeNull() + }) + + it('should return null if no selector', () => { + fixtureEl.innerHTML = '<div></div>' + + const testEl = fixtureEl.querySelector('div') + + expect(getSelectorFromElement(testEl)).toBeNull() + }) + }) + + describe('getElementFromSelector', () => { + it('should get element from data-bs-target', () => { + fixtureEl.innerHTML = [ + '<div id="test" data-bs-target=".target"></div>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) + }) + + it('should get element from href if no data-bs-target set', () => { + fixtureEl.innerHTML = [ + '<a id="test" href=".target"></a>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) + }) + + it('should return null if element not found', () => { + fixtureEl.innerHTML = '<a id="test" href=".target"></a>' + + const testEl = fixtureEl.querySelector('#test') + + expect(getElementFromSelector(testEl)).toBeNull() + }) + + it('should return null if no selector', () => { + fixtureEl.innerHTML = '<div></div>' + + const testEl = fixtureEl.querySelector('div') + + expect(getElementFromSelector(testEl)).toBeNull() + }) + }) + + describe('getMultipleElementsFromSelector', () => { + it('should get elements from data-bs-target', () => { + fixtureEl.innerHTML = [ + '<div id="test" data-bs-target=".target"></div>', + '<div class="target"></div>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + }) + + it('should get elements in array, from href if no data-bs-target set', () => { + fixtureEl.innerHTML = [ + '<a id="test" href=".target"></a>', + '<div class="target"></div>', + '<div class="target"></div>' + ].join('') + + const testEl = fixtureEl.querySelector('#test') + + expect(getMultipleElementsFromSelector(testEl)).toEqual(Array.from(fixtureEl.querySelectorAll('.target'))) + }) + + it('should return empty array if elements not found', () => { + fixtureEl.innerHTML = '<a id="test" href=".target"></a>' + + const testEl = fixtureEl.querySelector('#test') + + expect(getMultipleElementsFromSelector(testEl)).toHaveSize(0) + }) + + it('should return empty array if no selector', () => { + fixtureEl.innerHTML = '<div></div>' + + const testEl = fixtureEl.querySelector('div') + + expect(getMultipleElementsFromSelector(testEl)).toHaveSize(0) + }) + }) +}) diff --git a/js/tests/unit/util/focustrap.spec.js b/js/tests/unit/util/focustrap.spec.js index bedd124c9e..6178f7a5c3 100644 --- a/js/tests/unit/util/focustrap.spec.js +++ b/js/tests/unit/util/focustrap.spec.js @@ -1,6 +1,6 @@ import FocusTrap from '../../../src/util/focustrap' import EventHandler from '../../../src/dom/event-handler' -import SelectorEngine from '../../../src/dom/selector-engine' +import { SelectorEngine } from '../../../src/dom/selector-engine' import { clearFixture, createEvent, getFixture } from '../../helpers/fixture' describe('FocusTrap', () => { diff --git a/js/tests/unit/util/index.spec.js b/js/tests/unit/util/index.spec.js index 6edc494335..202c72061d 100644 --- a/js/tests/unit/util/index.spec.js +++ b/js/tests/unit/util/index.spec.js @@ -22,119 +22,6 @@ describe('Util', () => { }) }) - describe('getSelectorFromElement', () => { - it('should get selector from data-bs-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-bs-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if no data-bs-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should get selector from href if data-bs-target equal to #', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('.target') - }) - - it('should return null if a selector from a href is a url without an anchor', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href="foo/bar.html"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - - it('should return the anchor if a selector from a href is a url', () => { - fixtureEl.innerHTML = [ - '<a id="test" data-bs-target="#" href="foo/bar.html#target"></a>', - '<div id="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toEqual('#target') - }) - - it('should return null if selector not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getSelectorFromElement(testEl)).toBeNull() - }) - }) - - describe('getElementFromSelector', () => { - it('should get element from data-bs-target', () => { - fixtureEl.innerHTML = [ - '<div id="test" data-bs-target=".target"></div>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should get element from href if no data-bs-target set', () => { - fixtureEl.innerHTML = [ - '<a id="test" href=".target"></a>', - '<div class="target"></div>' - ].join('') - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toEqual(fixtureEl.querySelector('.target')) - }) - - it('should return null if element not found', () => { - fixtureEl.innerHTML = '<a id="test" href=".target"></a>' - - const testEl = fixtureEl.querySelector('#test') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - - it('should return null if no selector', () => { - fixtureEl.innerHTML = '<div></div>' - - const testEl = fixtureEl.querySelector('div') - - expect(Util.getElementFromSelector(testEl)).toBeNull() - }) - }) - describe('getTransitionDurationFromElement', () => { it('should get transition from element', () => { fixtureEl.innerHTML = '<div style="transition: all 300ms ease-out;"></div>' |