]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Changes to Alert component to match the others (#33402)
authorGeoSot <geo.sotis@gmail.com>
Mon, 28 Jun 2021 13:34:47 +0000 (16:34 +0300)
committerGitHub <noreply@github.com>
Mon, 28 Jun 2021 13:34:47 +0000 (16:34 +0300)
Alert.js: Refactor code to match the other components
* Use this._element
* Remove handleDismiss method and keep its functionality on event
* Change JqueryInterface to be more generic
* Correct docs to be aligned with code, and add undocumented functionality
* Update alerts.md

Co-authored-by: XhmikosR <xhmikosr@gmail.com>
js/src/alert.js
js/tests/unit/alert.spec.js
site/content/docs/5.0/components/alerts.md

index 75dbec71bcf5234b0f9e9fc0be54671a712412f3..0bbe62af59b1edfc1dfcf8dcef2f860fdc1b596c 100644 (file)
@@ -7,7 +7,8 @@
 
 import {
   defineJQueryPlugin,
-  getElementFromSelector
+  getElementFromSelector,
+  isDisabled
 } from './util/index'
 import EventHandler from './dom/event-handler'
 import BaseComponent from './base-component'
@@ -48,38 +49,24 @@ class Alert extends BaseComponent {
 
   // Public
 
-  close(element) {
-    const rootElement = element ? this._getRootElement(element) : this._element
-    const customEvent = this._triggerCloseEvent(rootElement)
+  close() {
+    const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE)
 
-    if (customEvent === null || customEvent.defaultPrevented) {
+    if (closeEvent.defaultPrevented) {
       return
     }
 
-    this._removeElement(rootElement)
-  }
-
-  // Private
-
-  _getRootElement(element) {
-    return getElementFromSelector(element) || element.closest(`.${CLASS_NAME_ALERT}`)
-  }
+    this._element.classList.remove(CLASS_NAME_SHOW)
 
-  _triggerCloseEvent(element) {
-    return EventHandler.trigger(element, EVENT_CLOSE)
+    const isAnimated = this._element.classList.contains(CLASS_NAME_FADE)
+    this._queueCallback(() => this._destroyElement(), this._element, isAnimated)
   }
 
-  _removeElement(element) {
-    element.classList.remove(CLASS_NAME_SHOW)
-
-    const isAnimated = element.classList.contains(CLASS_NAME_FADE)
-    this._queueCallback(() => this._destroyElement(element), element, isAnimated)
-  }
-
-  _destroyElement(element) {
-    element.remove()
-
-    EventHandler.trigger(element, EVENT_CLOSED)
+  // Private
+  _destroyElement() {
+    this._element.remove()
+    EventHandler.trigger(this._element, EVENT_CLOSED)
+    this.dispose()
   }
 
   // Static
@@ -88,20 +75,16 @@ class Alert extends BaseComponent {
     return this.each(function () {
       const data = Alert.getOrCreateInstance(this)
 
-      if (config === 'close') {
-        data[config](this)
+      if (typeof config !== 'string') {
+        return
       }
-    })
-  }
 
-  static handleDismiss(alertInstance) {
-    return function (event) {
-      if (event) {
-        event.preventDefault()
+      if (data[config] === undefined || config.startsWith('_') || config === 'constructor') {
+        throw new TypeError(`No method named "${config}"`)
       }
 
-      alertInstance.close(this)
-    }
+      data[config](this)
+    })
   }
 }
 
@@ -111,7 +94,19 @@ class Alert extends BaseComponent {
  * ------------------------------------------------------------------------
  */
 
-EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, Alert.handleDismiss(new Alert()))
+EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DISMISS, function (event) {
+  if (['A', 'AREA'].includes(this.tagName)) {
+    event.preventDefault()
+  }
+
+  if (isDisabled(this)) {
+    return
+  }
+
+  const target = getElementFromSelector(this) || this.closest(`.${CLASS_NAME_ALERT}`)
+  const alert = Alert.getOrCreateInstance(target)
+  alert.close()
+})
 
 /**
  * ------------------------------------------------------------------------
index 53dc0700c90d29d2fffc3de654298624c760f4cc..72cd23d89e79ee3b7b6d239d6da294e562645410 100644 (file)
@@ -2,7 +2,7 @@ import Alert from '../../src/alert'
 import { getTransitionDurationFromElement } from '../../src/util/index'
 
 /** Test helpers */
-import { getFixture, clearFixture, jQueryMock } from '../helpers/fixture'
+import { clearFixture, getFixture, jQueryMock } from '../helpers/fixture'
 
 describe('Alert', () => {
   let fixtureEl
@@ -102,25 +102,20 @@ describe('Alert', () => {
     it('should not remove alert if close event is prevented', done => {
       fixtureEl.innerHTML = '<div class="alert"></div>'
 
-      const alertEl = document.querySelector('.alert')
+      const getAlert = () => document.querySelector('.alert')
+      const alertEl = getAlert()
       const alert = new Alert(alertEl)
 
-      const endTest = () => {
+      alertEl.addEventListener('close.bs.alert', event => {
+        event.preventDefault()
         setTimeout(() => {
-          expect(alert._removeElement).not.toHaveBeenCalled()
+          expect(getAlert()).not.toBeNull()
           done()
         }, 10)
-      }
-
-      spyOn(alert, '_removeElement')
-
-      alertEl.addEventListener('close.bs.alert', event => {
-        event.preventDefault()
-        endTest()
       })
 
       alertEl.addEventListener('closed.bs.alert', () => {
-        endTest()
+        throw new Error('should not fire closed event')
       })
 
       alert.close()
@@ -167,9 +162,9 @@ describe('Alert', () => {
       jQueryMock.fn.alert = Alert.jQueryInterface
       jQueryMock.elements = [alertEl]
 
+      expect(Alert.getInstance(alertEl)).toBeNull()
       jQueryMock.fn.alert.call(jQueryMock, 'close')
 
-      expect(Alert.getInstance(alertEl)).not.toBeNull()
       expect(fixtureEl.querySelector('.alert')).toBeNull()
     })
 
index a7e52f5f8937fbc36bca486af979742452e0648f..51feb966eded38fcf2f3eac5042f0195eafdb8fd 100644 (file)
@@ -147,35 +147,39 @@ Loop that generates the modifier classes with the `alert-variant()` mixin.
 
 ## JavaScript behavior
 
-### Triggers
+### Initialize
 
-Enable dismissal of an alert via JavaScript:
+Initialize elements as alerts
 
 ```js
 var alertList = document.querySelectorAll('.alert')
-alertList.forEach(function (alert) {
-  new bootstrap.Alert(alert)
+var alerts =  [].slice.call(alertList).map(function (element) {
+  return new bootstrap.Alert(element)
 })
 ```
+{{< callout info >}}
+For the sole purpose of dismissing an alert, it isn't necessary to initialize the component manually via the JS API. By making use of `data-bs-dismiss="alert"`, the component will be initialized automatically and properly dismissed.
 
-Or with `data` attributes on a button **within the alert**, as demonstrated above:
+See the [triggers](#triggers) section for more details.
+{{< /callout >}}
+
+### Triggers
+
+Dismissal can be achieved with `data` attributes on a button **within the alert** as demonstrated above:
 
 ```html
 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
 ```
 
-Note that closing an alert will remove it from the DOM.
-
-### Methods
-
-You can create an alert instance with the alert constructor, for example:
+or on a button **outside the alert** using the `data-bs-target` as demonstrated above:
 
-```js
-var myAlert = document.getElementById('myAlert')
-var bsAlert = new bootstrap.Alert(myAlert)
+```html
+<button type="button" class="btn-close" data-bs-dismiss="alert" data-bs-target="#my-alert" aria-label="Close"></button>
 ```
 
-This makes an alert listen for click events on descendant elements which have the `data-bs-dismiss="alert"` attribute. (Not necessary when using the data-api's auto-initialization.)
+**Note that closing an alert will remove it from the DOM.**
+
+### Methods
 
 <table class="table">
   <thead>