]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
Implement `data-dismiss="toast"` to allow user to interact itself with the component...
authorLaussel Loïc <loic.laussel@orange.com>
Fri, 31 Aug 2018 07:18:28 +0000 (09:18 +0200)
committerXhmikosR <xhmikosr@gmail.com>
Tue, 13 Nov 2018 06:47:32 +0000 (08:47 +0200)
js/src/toast.js
js/tests/unit/toast.js
js/tests/visual/toast.html
site/docs/4.1/components/toasts.md

index cb6de974b5ccd2f670ac1a452185b6a33bd4c1b9..1e70e091f2bb94bb524ebb9b617e76dc4d22d52e 100644 (file)
@@ -22,10 +22,11 @@ const Toast = (($) => {
   const JQUERY_NO_CONFLICT = $.fn[NAME]
 
   const Event = {
-    HIDE   : `hide${EVENT_KEY}`,
-    HIDDEN : `hidden${EVENT_KEY}`,
-    SHOW   : `show${EVENT_KEY}`,
-    SHOWN  : `shown${EVENT_KEY}`
+    CLICK_DISMISS : `click.dismiss${EVENT_KEY}`,
+    HIDE          : `hide${EVENT_KEY}`,
+    HIDDEN        : `hidden${EVENT_KEY}`,
+    SHOW          : `show${EVENT_KEY}`,
+    SHOWN         : `shown${EVENT_KEY}`
   }
 
   const ClassName = {
@@ -49,6 +50,10 @@ const Toast = (($) => {
     }
   }
 
+  const Selector = {
+    DATA_DISMISS : '[data-dismiss="toast"]'
+  }
+
   /**
    * ------------------------------------------------------------------------
    * Class Definition
@@ -60,6 +65,7 @@ const Toast = (($) => {
       this._element = element
       this._config  = this._getConfig(config)
       this._timeout = null
+      this._setListeners()
     }
 
     // Getters
@@ -104,30 +110,20 @@ const Toast = (($) => {
       }, this._config.delay.show)
     }
 
-    hide() {
+    hide(withoutTimeout) {
       if (!this._element.classList.contains(ClassName.SHOW)) {
         return
       }
 
       $(this._element).trigger(Event.HIDE)
 
-      const complete = () => {
-        $(this._element).trigger(Event.HIDDEN)
+      if (withoutTimeout) {
+        this._close()
+      } else {
+        this._timeout = setTimeout(() => {
+          this._close()
+        }, this._config.delay.hide)
       }
-
-      this._timeout = setTimeout(() => {
-        this._element.classList.remove(ClassName.SHOW)
-
-        if (this._config.animation) {
-          const transitionDuration = Util.getTransitionDurationFromElement(this._element)
-
-          $(this._element)
-            .one(Util.TRANSITION_END, complete)
-            .emulateTransitionEnd(transitionDuration)
-        } else {
-          complete()
-        }
-      }, this._config.delay.hide)
     }
 
     dispose() {
@@ -138,6 +134,8 @@ const Toast = (($) => {
         this._element.classList.remove(ClassName.SHOW)
       }
 
+      $(this._element).off(Event.CLICK_DISMISS)
+
       $.removeData(this._element, DATA_KEY)
       this._element = null
       this._config  = null
@@ -168,6 +166,32 @@ const Toast = (($) => {
       return config
     }
 
+    _setListeners() {
+      $(this._element).on(
+        Event.CLICK_DISMISS,
+        Selector.DATA_DISMISS,
+        () => this.hide(true)
+      )
+    }
+
+    _close() {
+      const complete = () => {
+        $(this._element).trigger(Event.HIDDEN)
+      }
+
+      this._element.classList.remove(ClassName.SHOW)
+
+      if (this._config.animation) {
+        const transitionDuration = Util.getTransitionDurationFromElement(this._element)
+
+        $(this._element)
+          .one(Util.TRANSITION_END, complete)
+          .emulateTransitionEnd(transitionDuration)
+      } else {
+        complete()
+      }
+    }
+
     // Static
 
     static _jQueryInterface(config) {
index 873661c76f63b7dfeb735d187a3d185dac3d4b5d..d9c5e1fb6ece89bf8919a75aaff7d675b7d874ae 100644 (file)
@@ -232,4 +232,33 @@ $(function () {
     })
       .bootstrapToast('show')
   })
+
+
+  QUnit.test('should close toast when close element with data-dismiss attribute is set', function (assert) {
+    assert.expect(2)
+    var done = assert.async()
+
+    var toastHtml =
+      '<div class="toast" data-delay="1" data-autohide="false" data-animation="false">' +
+        '<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">' +
+          'close' +
+        '</button>' +
+      '</div>'
+
+    var $toast = $(toastHtml)
+      .bootstrapToast()
+      .appendTo($('#qunit-fixture'))
+
+    $toast
+      .on('shown.bs.toast', function () {
+        assert.strictEqual($toast.hasClass('show'), true)
+        var button = $toast.find('.close')
+        button.trigger('click')
+      })
+      .on('hidden.bs.toast', function () {
+        assert.strictEqual($toast.hasClass('show'), false)
+        done()
+      })
+      .bootstrapToast('show')
+  })
 })
index 6897022c06edb8078806ca8a64d4ab6eaa1a9c24..902194617b6a9c3476cd2f6bae7ee4e7f124c259 100644 (file)
     </div>
 
     <div class="notifications">
-      <div id="toastAutoHide" class="toast">
+      <div id="toastAutoHide" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
         <div class="toast-header">
           <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
           <strong class="mr-auto">Bootstrap</strong>
           <small>11 mins ago</small>
+          <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+            <span aria-hidden="true">&times;</span>
+          </button>
         </div>
         <div class="toast-body">
           Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds
         </div>
       </div>
 
-      <div class="toast" data-autohide="false">
+      <div class="toast" data-autohide="false" role="alert" aria-live="assertive" aria-atomic="true">
         <div class="toast-header">
           <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
           <strong class="mr-auto">Bootstrap</strong>
           <small class="text-muted">2 seconds ago</small>
+          <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+            <span aria-hidden="true">&times;</span>
+          </button>
         </div>
         <div class="toast-body">
           Heads up, toasts will stack automatically
index 54e0a1c52cd10f74b639434206f20dbef905f85e..84359084d299bbefe98d92c96267a656476dafaa 100644 (file)
@@ -24,11 +24,14 @@ A basic toast can include a header (though it doesn't strictly need one) with wh
 
 <div class="bg-light">
 {% capture example %}
-<div class="toast">
+<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
   <div class="toast-header">
     <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
     <strong class="mr-auto">Bootstrap</strong>
     <small>11 mins ago</small>
+    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+      <span aria-hidden="true">&times;</span>
+    </button>
   </div>
   <div class="toast-body">
     Hello, world! This is a toast message.
@@ -42,11 +45,14 @@ They're slightly translucent, too, so they blend over whatever they might appear
 
 <div class="bg-dark">
 {% capture example %}
-<div class="toast">
+<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
   <div class="toast-header">
     <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
     <strong class="mr-auto">Bootstrap</strong>
     <small class="text-muted">11 mins ago</small>
+    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+      <span aria-hidden="true">&times;</span>
+    </button>
   </div>
   <div class="toast-body">
     Hello, world! This is a toast message.
@@ -60,22 +66,28 @@ Plus, they'll easily stack.
 
 <div class="bg-light">
 {% capture example %}
-<div class="toast">
+<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
   <div class="toast-header">
     <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
     <strong class="mr-auto">Bootstrap</strong>
     <small class="text-muted">just now</small>
+    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+      <span aria-hidden="true">&times;</span>
+    </button>
   </div>
   <div class="toast-body">
     See? Just like this.
   </div>
 </div>
 
-<div class="toast">
+<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
   <div class="toast-header">
     <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
     <strong class="mr-auto">Bootstrap</strong>
     <small class="text-muted">2 seconds ago</small>
+    <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+      <span aria-hidden="true">&times;</span>
+    </button>
   </div>
   <div class="toast-body">
     Heads up, toasts will stack automatically
@@ -88,10 +100,12 @@ Plus, they'll easily stack.
 ## Accessibility
 
 Toasts are intended to be small interruptions to your visitors or users, so to help those on screen readers, you should wrap your toasts in an [`aria-live` region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions). This allows screen readers the ability to see suggested interruptions without any visual cues.
+To improve accessibility level, we strongly recomend to use `autohide: false` and add a `close` button into the header to let user dismiss that element.
+You also need to adapt the `role` and `aria-live` level depending on the content. If it's an important message like error, use an `alert` role `assertive` otherwise use a role `status` with a `polite` level. 
 
 {% highlight html %}
-<div role="region" aria-live="polite">
-  <div class="toast">...</div>
+<div role="alert" aria-live="assertive" aria-atomic="true">
+  <div role="alert" aria-live="assertive" aria-atomic="true">...</div>
 </div>
 {% endhighlight %}
 
@@ -107,6 +121,9 @@ Place toasts with custom CSS as you need them. The top right is often used for n
       <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
       <strong class="mr-auto">Bootstrap</strong>
       <small>11 mins ago</small>
+      <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+        <span aria-hidden="true">&times;</span>
+      </button>
     </div>
     <div class="toast-body">
       Hello, world! This is a toast message.
@@ -126,22 +143,28 @@ For systems that generate more notifications, consider using a wrapping element
   <div style="position: absolute; top: 0; right: 0;">
 
     <!-- Then put toasts within -->
-    <div class="toast">
+    <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
       <div class="toast-header">
         <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
         <strong class="mr-auto">Bootstrap</strong>
         <small class="text-muted">just now</small>
+        <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+          <span aria-hidden="true">&times;</span>
+        </button>
       </div>
       <div class="toast-body">
         See? Just like this.
       </div>
     </div>
 
-    <div class="toast">
+    <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
       <div class="toast-header">
         <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
         <strong class="mr-auto">Bootstrap</strong>
         <small class="text-muted">2 seconds ago</small>
+        <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
+          <span aria-hidden="true">&times;</span>
+        </button>
       </div>
       <div class="toast-body">
         Heads up, toasts will stack automatically
@@ -162,11 +185,14 @@ You can also get fancy with flexbox utilities.
   <div class="d-flex justify-content-center" style="position: absolute; top: 0; right: 0; left: 0;">
 
     <!-- Then put toasts within -->
-    <div class="toast">
+    <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
       <div class="toast-header">
         <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
         <strong class="mr-auto">Bootstrap</strong>
         <small>11 mins ago</small>
+        <button type="button" class="close" data-dismiss="toast" aria-label="Close" style="">
+          <span aria-hidden="true">&times;</span>
+        </button>
       </div>
       <div class="toast-body">
         Hello, world! This is a toast message.