Complete toast implementation

This commit is contained in:
Daniel J. Summers 2021-08-07 17:36:29 -04:00
parent 0679fe04ef
commit 058c5e5b77
4 changed files with 94 additions and 49 deletions

View File

@ -9,21 +9,18 @@
<component :is="Component" />
</transition>
</router-view>
<button @click.prevent="showToast('howdy', 'danger')">Show toast</button>
</main>
<app-footer />
<div aria-live="polite" aria-atomic="true">
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
</div>
<app-toaster />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Toast } from 'bootstrap'
import AppFooter from './components/layout/AppFooter.vue'
import AppNav from './components/layout/AppNav.vue'
import AppToaster from './components/layout/AppToaster.vue'
import TitleBar from './components/layout/TitleBar.vue'
import 'bootstrap/dist/css/bootstrap.min.css'
@ -34,12 +31,8 @@ export default defineComponent({
components: {
AppFooter,
AppNav,
AppToaster,
TitleBar
},
setup () {
return {
showToast
}
}
})
@ -52,33 +45,6 @@ export default defineComponent({
export function yesOrNo (cond : boolean) : string {
return cond ? 'Yes' : 'No'
}
/**
* Show a toast notification
*
* @param text The text of the notification
* @param type The type of notification to show (defaults to 'success')
* @param heading The optional text to use for the heading
*/
export function showToast (text : string, type = 'success', heading : string | undefined) : void {
let header : HTMLDivElement | undefined
if (heading) {
header = document.createElement('div')
header.className = 'toast-header'
header.innerHTML = 'The Header'
}
const body = document.createElement('div')
body.className = 'toast-body'
body.innerHTML = text
const toast = document.createElement('div')
if (header) toast.appendChild(header)
toast.appendChild(body)
toast.className = `toast bg-${type} text-white`
;(document.getElementById('toasts') as HTMLDivElement).appendChild(toast)
new Toast(toast).show()
}
</script>
<style lang="sass">

View File

@ -0,0 +1,83 @@
<template>
<div aria-live="polite" aria-atomic="true">
<div class="toast-container position-absolute p-3 bottom-0 start-50 translate-middle-x" id="toasts"></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { Toast } from 'bootstrap'
/** Remove a toast once it's hidden */
const removeToast = (event : Event) => (event.target as HTMLDivElement).remove()
/** Create a toast, add it to the DOM, and show it */
const createToast = (level : 'success' | 'warning' | 'danger', message : string, process : string | undefined) => {
let header : HTMLDivElement | undefined
if (level !== 'success') {
// Create a heading, optionally including the process that generated the message
const heading = (typ : string) : string => {
const proc = process ? ` (${process})` : ''
return `<span class="me-auto"><strong>${typ.toUpperCase()}</strong>${proc}</span>`
}
header = document.createElement('div')
header.className = 'toast-header'
header.innerHTML = heading(level === 'warning' ? level : 'error')
// Include a close button, as these will not auto-close
const close = document.createElement('button')
close.type = 'button'
close.className = 'btn-close'
close.setAttribute('data-bs-dismiss', 'toast')
close.setAttribute('aria-label', 'Close')
header.appendChild(close)
}
const body = document.createElement('div')
body.className = 'toast-body'
body.innerHTML = message
const toastEl = document.createElement('div')
toastEl.className = `toast bg-${level} text-white`
toastEl.setAttribute('role', 'alert')
toastEl.setAttribute('aria-live', 'assertlive')
toastEl.setAttribute('aria-atomic', 'true')
toastEl.addEventListener('hidden.bs.toast', removeToast)
if (header) toastEl.appendChild(header)
toastEl.appendChild(body)
;(document.getElementById('toasts') as HTMLDivElement).appendChild(toastEl)
new Toast(toastEl, { autohide: level === 'success' }).show()
}
/**
* Generate a success toast
*
* @param message The message to be displayed
*/
export function toastSuccess (message : string) : void {
createToast('success', message, undefined)
}
/**
* Generate a warning toast
*
* @param message The message to be displayed
* @param process The process which generated the warning (optional)
*/
export function toastWarning (message : string, process : string | undefined) : void {
createToast('warning', message, process)
}
/**
* Generate an error toast
*
* @param message The message to be displayed
* @param process The process which generated the error (optional)
*/
export function toastError (message : string, process : string | undefined) : void {
createToast('danger', message, process)
}
export default defineComponent({
name: 'AppToaster'
})
</script>

View File

@ -9,6 +9,7 @@
<script lang="ts">
import { defineComponent, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { toastSuccess } from '@/components/layout/AppToaster.vue'
import { useStore } from '@/store'
export default defineComponent({
@ -19,8 +20,8 @@ export default defineComponent({
onMounted(() => {
store.commit('clearUser')
toastSuccess('Log Off Successful &nbsp; | &nbsp; <strong>Have a Nice Day!</strong>')
router.push('/')
// TODO: toast
})
return { }

View File

@ -3,8 +3,6 @@
<page-title title="Account Deletion Options" />
<h3>Account Deletion Options</h3>
<p v-if="error !== ''">{{error}}</p>
<h4>Option 1 &ndash; Delete Your Profile</h4>
<p>
Utilizing this option will remove your current employment profile and skills. This will preserve any success
@ -38,9 +36,10 @@
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { defineComponent } from 'vue'
import { useRouter } from 'vue-router'
import api, { LogOnSuccess } from '@/api'
import { toastError, toastSuccess } from '@/components/layout/AppToaster.vue'
import { useStore } from '@/store'
export default defineComponent({
@ -49,16 +48,13 @@ export default defineComponent({
const store = useStore()
const router = useRouter()
/** Error message encountered during actions */
const error = ref('')
/** Delete the profile only; redirect to home page on success */
const deleteProfile = async () => {
const resp = await api.profile.delete(store.state.user as LogOnSuccess)
if (typeof resp === 'string') {
error.value = resp
toastError(resp, 'Deleting Profile')
} else {
// TODO: notify
toastSuccess('Profile Deleted Successfully')
router.push('/citizen/dashboard')
}
}
@ -67,16 +63,15 @@ export default defineComponent({
const deleteAccount = async () => {
const resp = await api.citizen.delete(store.state.user as LogOnSuccess)
if (typeof resp === 'string') {
error.value = resp
toastError(resp, 'Deleting Account')
} else {
store.commit('clearUser')
// TODO: notify
toastSuccess('Account Deleted Successfully')
router.push('/so-long/success')
}
}
return {
error,
deleteProfile,
deleteAccount
}