]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Allow constructors to accept a CSS selector (#32245)
authorRohit Sharma <rohit2sharma95@gmail.com>
Mon, 22 Feb 2021 07:01:04 +0000 (12:31 +0530)
committerGitHub <noreply@github.com>
Mon, 22 Feb 2021 07:01:04 +0000 (09:01 +0200)
Co-authored-by: XhmikosR <xhmikosr@gmail.com>
Co-authored-by: Mark Otto <otto@github.com>
16 files changed:
js/src/base-component.js
js/src/collapse.js
js/src/modal.js
js/src/scrollspy.js
js/tests/unit/alert.spec.js
js/tests/unit/button.spec.js
js/tests/unit/carousel.spec.js
js/tests/unit/collapse.spec.js
js/tests/unit/dropdown.spec.js
js/tests/unit/modal.spec.js
js/tests/unit/scrollspy.spec.js
js/tests/unit/tab.spec.js
js/tests/unit/toast.spec.js
js/tests/unit/tooltip.spec.js
site/content/docs/5.0/getting-started/javascript.md
site/content/docs/5.0/migration.md

index 9de274bd0978561e2f63ea80dc58fae63869a3be..989a6415619557be73cbfeb6aa3978a074b12f38 100644 (file)
@@ -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() {
index 0a1b475470cda25dbd257d1414959eac2099abd6..f86166765924015a007b532591cda6bf19092355 100644 (file)
@@ -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
index 79a2f143a38b092a46a574a3950c31246a2618de..4f42e733eba5bd0eeab1a832defba1e035870a9c 100644 (file)
@@ -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
index 43a91e5e9326181bf4fb71f3eca5485750d50e24..0c51eab0fef6862fd61723d2cbb2f67c4ac80dd7 100644 (file)
@@ -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 = []
index 916c7fd07c6d4eb1e18d30b51fa1aa4033e1ba03..194da420e9b521c61701ac2535f6439e6b6abc0c 100644 (file)
@@ -15,6 +15,17 @@ describe('Alert', () => {
     clearFixture()
   })
 
+  it('should take care of element either passed as a CSS selector or DOM element', () => {
+    fixtureEl.innerHTML = '<div class="alert"></div>'
+
+    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')
   })
index e442fd90d0a1b4a299c0544302866dee1d7bc0e2..e7d92cb6d20883e0927305cdff160511b97849d3 100644 (file)
@@ -18,6 +18,16 @@ describe('Button', () => {
     clearFixture()
   })
 
+  it('should take care of element either passed as a CSS selector or DOM element', () => {
+    fixtureEl.innerHTML = '<button data-bs-toggle="button">Placeholder</button>'
+    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))
index 533e1ba7e2537625fb3f3d94c2aa971d4bc9cd35..c475489c0605487619462aeddfb5bf3f5a9f7c9d 100644 (file)
@@ -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 = '<div id="myCarousel" class="carousel slide"></div>'
+
+      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 = [
         '<div id="myCarousel" class="carousel slide">',
index cd30ed8daa8d247577428cfa30c036918e168128..bc7c157714db63275b6e7a2dafdd195df527cf61 100644 (file)
@@ -34,6 +34,17 @@ describe('Collapse', () => {
   })
 
   describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = '<div class="my-collapse"></div>'
+
+      const collapseEl = fixtureEl.querySelector('div.my-collapse')
+      const collapseBySelector = new Collapse('div.my-collapse')
+      const collapseByElement = new Collapse(collapseEl)
+
+      expect(collapseBySelector._element).toEqual(collapseEl)
+      expect(collapseByElement._element).toEqual(collapseEl)
+    })
+
     it('should allow jquery object in parent config', () => {
       fixtureEl.innerHTML = [
         '<div class="my-collapse">',
index 658cb65b04ad0697235fe7b337261d0f38bf54be..b7e771c950415a120988c509b5a109fb72b45a18 100644 (file)
@@ -40,6 +40,24 @@ describe('Dropdown', () => {
   })
 
   describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = [
+        '<div class="dropdown">',
+        '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown">Dropdown</button>',
+        '  <div class="dropdown-menu">',
+        '    <a class="dropdown-item" href="#">Link</a>',
+        '  </div>',
+        '</div>'
+      ].join('')
+
+      const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
+      const dropdownBySelector = new Dropdown('[data-bs-toggle="dropdown"]')
+      const dropdownByElement = new Dropdown(btnDropdown)
+
+      expect(dropdownBySelector._element).toEqual(btnDropdown)
+      expect(dropdownByElement._element).toEqual(btnDropdown)
+    })
+
     it('should add a listener on trigger which do not have data-bs-toggle="dropdown"', () => {
       fixtureEl.innerHTML = [
         '<div class="dropdown">',
index 8a159eef6c984e6b3ff53141dc64225d475627a3..7f16bfc1d077cf80f3a2a26d32726470b9230b07 100644 (file)
@@ -62,6 +62,19 @@ describe('Modal', () => {
     })
   })
 
+  describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
+
+      const modalEl = fixtureEl.querySelector('.modal')
+      const modalBySelector = new Modal('.modal')
+      const modalByElement = new Modal(modalEl)
+
+      expect(modalBySelector._element).toEqual(modalEl)
+      expect(modalByElement._element).toEqual(modalEl)
+    })
+  })
+
   describe('toggle', () => {
     it('should toggle a modal', done => {
       fixtureEl.innerHTML = '<div class="modal"><div class="modal-dialog"></div></div>'
index a00da485f8d28c5d58e5b5775af77eca3c908beb..f7258ba355b8cc60cccb59093fa26aa16ae0417b 100644 (file)
@@ -54,6 +54,17 @@ describe('ScrollSpy', () => {
   })
 
   describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = '<nav id="navigation"></nav><div class="content"></div>'
+
+      const sSpyEl = fixtureEl.querySelector('#navigation')
+      const sSpyBySelector = new ScrollSpy('#navigation')
+      const sSpyByElement = new ScrollSpy(sSpyEl)
+
+      expect(sSpyBySelector._element).toEqual(sSpyEl)
+      expect(sSpyByElement._element).toEqual(sSpyEl)
+    })
+
     it('should generate an id when there is not one', () => {
       fixtureEl.innerHTML = [
         '<nav></nav>',
index c52812f52eac9b2c61ff309330c1b2e1598c4118..35d17e16b972e636106f99fc956a263675a21ed5 100644 (file)
@@ -20,6 +20,22 @@ describe('Tab', () => {
     })
   })
 
+  describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = [
+        '<ul class="nav"><li><a href="#home" role="tab">Home</a></li></ul>',
+        '<ul><li id="home"></li></ul>'
+      ].join('')
+
+      const tabEl = fixtureEl.querySelector('[href="#home"]')
+      const tabBySelector = new Tab('[href="#home"]')
+      const tabByElement = new Tab(tabEl)
+
+      expect(tabBySelector._element).toEqual(tabEl)
+      expect(tabByElement._element).toEqual(tabEl)
+    })
+  })
+
   describe('show', () => {
     it('should activate element by tab id (using buttons, the preferred semantic way)', done => {
       fixtureEl.innerHTML = [
index f8ef6e54b4bd6fa30c7ddb6968117d704822c3cf..d298dc993113d7eb67df6603799ce8d26db0c100 100644 (file)
@@ -27,6 +27,17 @@ describe('Toast', () => {
   })
 
   describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = '<div class="toast"></div>'
+
+      const toastEl = fixtureEl.querySelector('.toast')
+      const toastBySelector = new Toast('.toast')
+      const toastByElement = new Toast(toastEl)
+
+      expect(toastBySelector._element).toEqual(toastEl)
+      expect(toastByElement._element).toEqual(toastEl)
+    })
+
     it('should allow to config in js', done => {
       fixtureEl.innerHTML = [
         '<div class="toast">',
index 84f5abcdade1018933f52d02afb968d5dff9eec7..7bf6aa3ab8370ed738ea51948506a21fc953aec7 100644 (file)
@@ -63,6 +63,17 @@ describe('Tooltip', () => {
   })
 
   describe('constructor', () => {
+    it('should take care of element either passed as a CSS selector or DOM element', () => {
+      fixtureEl.innerHTML = '<a href="#" id="tooltipEl" rel="tooltip" title="Nice and short title">'
+
+      const tooltipEl = fixtureEl.querySelector('#tooltipEl')
+      const tooltipBySelector = new Tooltip('#tooltipEl')
+      const tooltipByElement = new Tooltip(tooltipEl)
+
+      expect(tooltipBySelector._element).toEqual(tooltipEl)
+      expect(tooltipByElement._element).toEqual(tooltipEl)
+    })
+
     it('should not take care of disallowed data attributes', () => {
       fixtureEl.innerHTML = '<a href="#" rel="tooltip" data-bs-sanitize="false" title="Another tooltip">'
 
index 53845fdfc2216f1d1a8dc05e0dd43959a9ad51a5..f57a3aedf6333f6e8bdeb71afa0677b77a88b9d0 100644 (file)
@@ -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 selector as the first argument instead of a DOM element to initialize the plugin. Currently the element for the plugin is found by the `querySelector` method since our plugins support a 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**.
index 94f2214d70b6befbbab90ab98a886f55acfb3820..903b282cf62929d22b70b107c0cfe9023d40f896 100644 (file)
@@ -9,6 +9,17 @@ toc: true
 
 ## v5.0.0-beta3
 
+### JavaScript
+
+- All plugins can now accept a CSS selector as the first argument. You can either pass a 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.