Toaster-like Notifications

by tbreuss

Level: intermediate • Mithril.js Version: latest

This example shows toaster-like notifications with proper CSS fade-out and fade-in animation.

The original author of the example is tabula-rasa. It was taken from here, modified slightly and brought into a runnable form by tbreuss.

Live Example

Dependencies

Type Name URL
scriptmithril@latesthttps://unpkg.com/mithril@latest

helpers.js

// helpers.js
function guid() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}

Notifications.js

// Notifications.js
let state = {
  list: [],
  destroy(msg) {
    let index = state.list.findIndex(x => x.id === msg.id)
    state.list.splice(index, 1)
  }
}

function addSuccess(text, timeout = 3000) {
  state.list.push({ id: guid(), type: 'success', text, timeout })
}
function addInfo(text, timeout = 3000) {
  state.list.push({ id: guid(), type: 'info', text, timeout })
}
function addWarning(text, timeout = 3000) {
  state.list.push({ id: guid(), type: 'warning', text, timeout })
}
function addDanger(text, timeout = 3000) {
  state.list.push({ id: guid(), type: 'danger', text, timeout })
}

let Notifications = {
  view(vnode) {
    let ui = vnode.state
    return state.list ?
      m('.m-notifications', state.list.map((msg) => {
        return m('div', { key: msg.id }, m(Notification, msg)) //wrap in div with key for proper dom updates
      })) : null
  }
}

let Notification = {
  oninit(vnode) {
    setTimeout(() => {
      Notification.destroy(vnode)
    }, vnode.attrs.timeout)
  },
  notificationClass(type) {
    const types = ['info', 'warning', 'success', 'danger']
    if (types.indexOf(type) > -1)
      return type
    return 'info'
  },
  destroy(vnode) {
    vnode.dom.classList.add('destroy')
    setTimeout(() => {
      state.destroy(vnode.attrs)
      m.redraw()
    }, 300)
  },
  view(vnode) {
    let ui = vnode.state
    let msg = vnode.attrs
    return m('.m-notification', { class: ui.notificationClass(msg.type), onclick: () => { ui.destroy(vnode) } }, msg.text)
  }
}

Layout.js

// Layout.js
const Layout = {
  view: function (vnode) {
    return m('.layout', [
      vnode.children,
      m(Notifications),
    ]);
  }
}

Page.js

// Page.js
const Page = {
  view(vnode) {
    return m('.page', [
      m('h1', 'Toaster-like Notifications'),
      m('p', 'Click the buttons to see toasts in the lower right corner.'),
      m('div', m('button[type=button]', {
        onclick: () => {
          addSuccess('Hey, this is a success message!', 5000)
        }},
        'Show success message'
      )),
      m('div', m('button[type=button]', {
        onclick: () => {
          addWarning('And this is a warning message!', 5000)
        }},
        'Show warning message'
      )),
      m('div', m('button[type=button]', {
        onclick: () => {
          addInfo('Wow, this a info message!', 5000)
        }},
        'Show info message'
      )),
      m('div', m('button[type=button]', {
        onclick: () => {
          addDanger('And this is a danger message!', 5000)
        }},
        'Show danger message'
      )),
    ])
  }
}

app.js

// app.js

addInfo('Info message!')
addWarning('Warning message!')
addDanger('This is danger message! Take care.')
addSuccess('Operation successful.')

m.route(document.body, '/', {
  '/': {
    render: () => m(Layout, m(Page))
  }
})

CSS

@import 'https://unpkg.com/water.css@2/out/water.min.css';

button {
  margin: 0.25rem 0;
}

.m-notifications {
  position: fixed;
  bottom: 20px;
  right: 20px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  z-index: 10;
}

.m-notification {
  width: auto;
  margin-bottom: 0.25rem;
  max-width: 400px;
  cursor: pointer;
  animation: fade-in 0.3s;
}

.m-notification:hover {
  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
}

.m-notification.destroy {
  animation: fade-out 0.3s;
}

.m-notification.info {
  color: #0c5460;
  background-color: #d1ecf1;
  padding: 0.75rem 1.25rem;
  border: 1px solid #bee5eb;
  border-radius: 0.25rem;
}

.m-notification.warning {
  color: #856404;
  background-color: #fff3cd;
  padding: 0.75rem 1.25rem;
  border: 1px solid #ffeeba;
  border-radius: 0.25rem;
}

.m-notification.danger {
  color: #721c24;
  background-color: #f8d7da;
  padding: 0.75rem 1.25rem;
  border: 1px solid #f5c6cb;
  border-radius: 0.25rem;
}

.m-notification.success {
  color: #155724;
  background-color: #d4edda;
  padding: 0.75rem 1.25rem;
  border: 1px solid #c3e6cb;
  border-radius: 0.25rem;
}

@keyframes fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@keyframes fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

As a prerequisite for this snippet, the latest version of Mithril.js framework is required. The example targets intermediate users, that are familiar with with most aspects of the framework.

Here we can see use cases of different Mithril.js API methods like m.redraw or m.route, besides the centrepiece m() hyperscript function. Moreover, it shows how the lifecycle method oninit can be used (better known as hook).

The example was written by tbreuss, last edits were made on 09 March 2022. The author has contributed some more snippets. Click here to see them all.

Contribute

Did you note a typo or something else? So let me know by opening an issue. Or much better: just fork the repository on GitHub, push your commits and send a pull request. Ready to start your work? Then click on the edit link below. Thanks in advance!

See more code examples  •  Edit this example on GitHub