Skip to content

Boom toasted #27155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions js/src/toast.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -49,6 +50,10 @@ const Toast = (($) => {
}
}

const Selector = {
DATA_DISMISS : '[data-dismiss="toast"]'
}

/**
* ------------------------------------------------------------------------
* Class Definition
Expand All @@ -60,6 +65,7 @@ const Toast = (($) => {
this._element = element
this._config = this._getConfig(config)
this._timeout = null
this._setListeners()
}

// Getters
Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
29 changes: 29 additions & 0 deletions js/tests/unit/toast.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
10 changes: 8 additions & 2 deletions js/tests/visual/toast.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,28 @@ <h1>Toast <small>Bootstrap Visual Test</small></h1>
</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
Expand Down
44 changes: 35 additions & 9 deletions site/docs/4.1/components/toasts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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.
Expand All @@ -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 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.
Expand All @@ -60,22 +66,28 @@ Plus, they'll easily stack.

<div class="bg-light">
{% capture example %}
<div class="toast">
<div 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 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
Expand All @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"to let users" (plural i'd say)"

Also, I wouldn't say it's necessarily an accessibility improvement. toasts/notifications that appear, are announced, and autohide are common and well supported by AT. there ARE some aspects, like users with cognitive disabilities or users that employ screen magnification without AT capabilities that may have issues with toasts, but that's a much larger discussion than just saying "don't hide it". those users would generally benefit more from explicit alert dialogs rather than toasts (dialogs that are modal, and shift focus to the dialog, etc)

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may be more terse and unambiguous at the end to say:

...use role="alert" aria-live="assertive", otherwise use role="status" aria-live="polite"...


{% 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 %}

Expand All @@ -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.
Expand All @@ -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 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 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
Expand All @@ -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 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.
Expand Down