From e9fc85724ab719c8f9f6b00f405bb0101c1dc973 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Tue, 24 Nov 2020 18:51:58 +0530 Subject: [PATCH 1/9] Allow constructors to accept a CSS selector --- js/src/base-component.js | 4 +++- js/src/collapse.js | 6 +++--- js/src/modal.js | 2 +- js/src/scrollspy.js | 2 +- site/content/docs/5.0/getting-started/javascript.md | 9 +++++++++ 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/js/src/base-component.js b/js/src/base-component.js index 9de274bd0978..989a64156195 100644 --- a/js/src/base-component.js +++ b/js/src/base-component.js @@ -17,12 +17,14 @@ const VERSION = '5.0.0-beta2' class BaseComponent { constructor(element) { + element = typeof element === 'string' ? document.querySelector(element) : element + if (!element) { return } this._element = element - Data.setData(element, this.constructor.DATA_KEY, this) + Data.setData(this._element, this.constructor.DATA_KEY, this) } dispose() { diff --git a/js/src/collapse.js b/js/src/collapse.js index 0a1b475470cd..f86166765924 100644 --- a/js/src/collapse.js +++ b/js/src/collapse.js @@ -72,8 +72,8 @@ class Collapse extends BaseComponent { this._isTransitioning = false this._config = this._getConfig(config) this._triggerArray = SelectorEngine.find( - `${SELECTOR_DATA_TOGGLE}[href="#${element.id}"],` + - `${SELECTOR_DATA_TOGGLE}[data-bs-target="#${element.id}"]` + `${SELECTOR_DATA_TOGGLE}[href="#${this._element.id}"],` + + `${SELECTOR_DATA_TOGGLE}[data-bs-target="#${this._element.id}"]` ) const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE) @@ -82,7 +82,7 @@ class Collapse extends BaseComponent { const elem = toggleList[i] const selector = getSelectorFromElement(elem) const filterElement = SelectorEngine.find(selector) - .filter(foundElem => foundElem === element) + .filter(foundElem => foundElem === this._element) if (selector !== null && filterElement.length) { this._selector = selector diff --git a/js/src/modal.js b/js/src/modal.js index 79a2f143a38b..4f42e733eba5 100644 --- a/js/src/modal.js +++ b/js/src/modal.js @@ -83,7 +83,7 @@ class Modal extends BaseComponent { super(element) this._config = this._getConfig(config) - this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, element) + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element) this._backdrop = null this._isShown = false this._isBodyOverflowing = false diff --git a/js/src/scrollspy.js b/js/src/scrollspy.js index 43a91e5e9326..0c51eab0fef6 100644 --- a/js/src/scrollspy.js +++ b/js/src/scrollspy.js @@ -68,7 +68,7 @@ const METHOD_POSITION = 'position' class ScrollSpy extends BaseComponent { constructor(element, config) { super(element) - this._scrollElement = element.tagName === 'BODY' ? window : element + this._scrollElement = this._element.tagName === 'BODY' ? window : this._element this._config = this._getConfig(config) this._selector = `${this._config.target} ${SELECTOR_NAV_LINKS}, ${this._config.target} ${SELECTOR_LIST_ITEMS}, ${this._config.target} .${CLASS_NAME_DROPDOWN_ITEM}` this._offsets = [] diff --git a/site/content/docs/5.0/getting-started/javascript.md b/site/content/docs/5.0/getting-started/javascript.md index 53845fdfc221..1d16c54620f5 100644 --- a/site/content/docs/5.0/getting-started/javascript.md +++ b/site/content/docs/5.0/getting-started/javascript.md @@ -93,6 +93,15 @@ var modal = new bootstrap.Modal(myModalEl, { keyboard: false }) // initialized w If you'd like to get a particular plugin instance, each plugin exposes a `getInstance` method. In order to retrieve it directly from an element, do this: `bootstrap.Popover.getInstance(myPopoverEl)`. +### CSS selectors in constructors + +You can also use a CSS selectors as the first argument instead of a DOM element to initialize the plugin. Currently the element for the plugin is found by `querySelector` method since the plugins support single element only. + +```js +var modal = new bootstrap.Modal('#myModal') +var dropdown = new bootstrap.Dropdown('[data-bs-toggle="dropdown"]') +``` + ### Asynchronous functions and transitions All programmatic API methods are **asynchronous** and return to the caller once the transition is started but **before it ends**. From c25a7672beda0b0493fc095b9def80345f946533 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Sun, 6 Dec 2020 00:46:06 +0530 Subject: [PATCH 2/9] Add migration note for CSS selectors --- site/content/docs/5.0/migration.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/site/content/docs/5.0/migration.md b/site/content/docs/5.0/migration.md index 94f2214d70b6..130ec013c634 100644 --- a/site/content/docs/5.0/migration.md +++ b/site/content/docs/5.0/migration.md @@ -9,6 +9,17 @@ toc: true ## v5.0.0-beta3 +### JavaScript + +- All plugins are now allowed to accept a CSS selector as the first argument. You can either pass DOM element or any valid CSS selector to create a new instance of the plugin. + +```js +var modal = new bootstrap.Modal('#myModal') +var dropdown = new bootstrap.Dropdown('[data-bs-toggle="dropdown"]') +``` + +## v5.0.0-beta2 + ### Utilities - Dropped the `0` entry in `$border-widths` map to remove the duplicated `.border-0` class. From 57f187994a7bdda1cad3edf624e62bd6d652bb66 Mon Sep 17 00:00:00 2001 From: Rohit Sharma Date: Sun, 6 Dec 2020 01:09:28 +0530 Subject: [PATCH 3/9] Add unit tests --- js/tests/unit/alert.spec.js | 11 +++++++++++ js/tests/unit/button.spec.js | 10 ++++++++++ js/tests/unit/carousel.spec.js | 11 +++++++++++ js/tests/unit/collapse.spec.js | 11 +++++++++++ js/tests/unit/dropdown.spec.js | 18 ++++++++++++++++++ js/tests/unit/modal.spec.js | 13 +++++++++++++ js/tests/unit/scrollspy.spec.js | 11 +++++++++++ js/tests/unit/tab.spec.js | 16 ++++++++++++++++ js/tests/unit/toast.spec.js | 11 +++++++++++ js/tests/unit/tooltip.spec.js | 11 +++++++++++ 10 files changed, 123 insertions(+) diff --git a/js/tests/unit/alert.spec.js b/js/tests/unit/alert.spec.js index 916c7fd07c6d..eee2b6a38b42 100644 --- a/js/tests/unit/alert.spec.js +++ b/js/tests/unit/alert.spec.js @@ -15,6 +15,17 @@ describe('Alert', () => { clearFixture() }) + it('should take care of element either passed as css selector or DOM element', () => { + fixtureEl.innerHTML = '
' + + const alertEl = fixtureEl.querySelector('.alert') + const alertBySelector = new Alert('.alert') + const alertByElement = new Alert(alertEl) + + expect(alertBySelector._element).toEqual(alertEl) + expect(alertByElement._element).toEqual(alertEl) + }) + it('should return version', () => { expect(typeof Alert.VERSION).toEqual('string') }) diff --git a/js/tests/unit/button.spec.js b/js/tests/unit/button.spec.js index e442fd90d0a1..e7d92cb6d208 100644 --- a/js/tests/unit/button.spec.js +++ b/js/tests/unit/button.spec.js @@ -18,6 +18,16 @@ describe('Button', () => { clearFixture() }) + it('should take care of element either passed as a CSS selector or DOM element', () => { + fixtureEl.innerHTML = '' + const buttonEl = fixtureEl.querySelector('[data-bs-toggle="button"]') + const buttonBySelector = new Button('[data-bs-toggle="button"]') + const buttonByElement = new Button(buttonEl) + + expect(buttonBySelector._element).toEqual(buttonEl) + expect(buttonByElement._element).toEqual(buttonEl) + }) + describe('VERSION', () => { it('should return plugin version', () => { expect(Button.VERSION).toEqual(jasmine.any(String)) diff --git a/js/tests/unit/carousel.spec.js b/js/tests/unit/carousel.spec.js index 533e1ba7e253..c475489c0605 100644 --- a/js/tests/unit/carousel.spec.js +++ b/js/tests/unit/carousel.spec.js @@ -52,6 +52,17 @@ describe('Carousel', () => { }) describe('constructor', () => { + it('should take care of element either passed as a CSS selector or DOM element', () => { + fixtureEl.innerHTML = '' + + const carouselEl = fixtureEl.querySelector('#myCarousel') + const carouselBySelector = new Carousel('#myCarousel') + const carouselByElement = new Carousel(carouselEl) + + expect(carouselBySelector._element).toEqual(carouselEl) + expect(carouselByElement._element).toEqual(carouselEl) + }) + it('should go to next item if right arrow key is pressed', done => { fixtureEl.innerHTML = [ '