Skip to content

Commit

Permalink
implement data-dismiss="toast" to allow user to interact itself with …
Browse files Browse the repository at this point in the history
…the component (#27155)
  • Loading branch information
Lausselloic authored and XhmikosR committed Oct 23, 2018
1 parent ce80dd0 commit 4322c88
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 32 deletions.
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 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.
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 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.
Expand All @@ -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
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.
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 %}

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

0 comments on commit 4322c88

Please sign in to comment.