1
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-08-10 15:44:51 +02:00

Implement data-dismiss="toast" to allow user to interact itself with the component (#27155)

This commit is contained in:
Laussel Loïc
2018-08-31 09:18:28 +02:00
committed by XhmikosR
parent 2f81ab007c
commit 4cac833447
4 changed files with 117 additions and 32 deletions

View File

@@ -22,10 +22,11 @@ const Toast = (($) => {
const JQUERY_NO_CONFLICT = $.fn[NAME] const JQUERY_NO_CONFLICT = $.fn[NAME]
const Event = { const Event = {
HIDE : `hide${EVENT_KEY}`, CLICK_DISMISS : `click.dismiss${EVENT_KEY}`,
HIDDEN : `hidden${EVENT_KEY}`, HIDE : `hide${EVENT_KEY}`,
SHOW : `show${EVENT_KEY}`, HIDDEN : `hidden${EVENT_KEY}`,
SHOWN : `shown${EVENT_KEY}` SHOW : `show${EVENT_KEY}`,
SHOWN : `shown${EVENT_KEY}`
} }
const ClassName = { const ClassName = {
@@ -49,6 +50,10 @@ const Toast = (($) => {
} }
} }
const Selector = {
DATA_DISMISS : '[data-dismiss="toast"]'
}
/** /**
* ------------------------------------------------------------------------ * ------------------------------------------------------------------------
* Class Definition * Class Definition
@@ -60,6 +65,7 @@ const Toast = (($) => {
this._element = element this._element = element
this._config = this._getConfig(config) this._config = this._getConfig(config)
this._timeout = null this._timeout = null
this._setListeners()
} }
// Getters // Getters
@@ -104,30 +110,20 @@ const Toast = (($) => {
}, this._config.delay.show) }, this._config.delay.show)
} }
hide() { hide(withoutTimeout) {
if (!this._element.classList.contains(ClassName.SHOW)) { if (!this._element.classList.contains(ClassName.SHOW)) {
return return
} }
$(this._element).trigger(Event.HIDE) $(this._element).trigger(Event.HIDE)
const complete = () => { if (withoutTimeout) {
$(this._element).trigger(Event.HIDDEN) 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() { dispose() {
@@ -138,6 +134,8 @@ const Toast = (($) => {
this._element.classList.remove(ClassName.SHOW) this._element.classList.remove(ClassName.SHOW)
} }
$(this._element).off(Event.CLICK_DISMISS)
$.removeData(this._element, DATA_KEY) $.removeData(this._element, DATA_KEY)
this._element = null this._element = null
this._config = null this._config = null
@@ -168,6 +166,32 @@ const Toast = (($) => {
return config 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
static _jQueryInterface(config) { static _jQueryInterface(config) {

View File

@@ -232,4 +232,33 @@ $(function () {
}) })
.bootstrapToast('show') .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')
})
}) })

View File

@@ -26,22 +26,28 @@
</div> </div>
<div class="notifications"> <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"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small>11 mins ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds Hello, world! This is a toast message with <strong>autohide</strong> in 2 seconds
</div> </div>
</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"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">2 seconds ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Heads up, toasts will stack automatically Heads up, toasts will stack automatically

View 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"> <div class="bg-light">
{% capture example %} {% capture example %}
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small>11 mins ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Hello, world! This is a toast message. 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"> <div class="bg-dark">
{% capture example %} {% capture example %}
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">11 mins ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Hello, world! This is a toast message. Hello, world! This is a toast message.
@@ -60,22 +66,28 @@ Plus, they'll easily stack.
<div class="bg-light"> <div class="bg-light">
{% capture example %} {% capture example %}
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">just now</small> <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>
<div class="toast-body"> <div class="toast-body">
See? Just like this. See? Just like this.
</div> </div>
</div> </div>
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">2 seconds ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Heads up, toasts will stack automatically Heads up, toasts will stack automatically
@@ -88,10 +100,12 @@ Plus, they'll easily stack.
## Accessibility ## 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. 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 %} {% highlight html %}
<div role="region" aria-live="polite"> <div role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast">...</div> <div role="alert" aria-live="assertive" aria-atomic="true">...</div>
</div> </div>
{% endhighlight %} {% 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=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small>11 mins ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Hello, world! This is a toast message. 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;"> <div style="position: absolute; top: 0; right: 0;">
<!-- Then put toasts within --> <!-- Then put toasts within -->
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">just now</small> <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>
<div class="toast-body"> <div class="toast-body">
See? Just like this. See? Just like this.
</div> </div>
</div> </div>
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small class="text-muted">2 seconds ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Heads up, toasts will stack automatically 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;"> <div class="d-flex justify-content-center" style="position: absolute; top: 0; right: 0; left: 0;">
<!-- Then put toasts within --> <!-- Then put toasts within -->
<div class="toast"> <div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <div class="toast-header">
<img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt=""> <img class="rounded mr-2" data-src="holder.js/20x20?size=1&text=.&bg=#007aff" alt="">
<strong class="mr-auto">Bootstrap</strong> <strong class="mr-auto">Bootstrap</strong>
<small>11 mins ago</small> <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>
<div class="toast-body"> <div class="toast-body">
Hello, world! This is a toast message. Hello, world! This is a toast message.