Skip to content
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

[Feature Request] Snackbar queue #2384

Assignees
Labels
C: VSnackbar VSnackbar framework Issues and Feature Requests that have needs framework-wide. P: elite sponsor T: feature A new feature

Comments

@mitar
Copy link
Contributor

mitar commented Oct 31, 2017

New Functionality

Currently it is not easy to handle interaction of multiple snackbar messages. They can overlap and hide each other. Moreover, because they are added as components inside other components, if parent component is removed (for example, because of a route change), snackbar message also disappears or is even not shown.

Improvements

I would propose that there is a global queue of snackbar messages. Maybe by sending a Vue event. Some options could then be to display messages one after the other, or display them stacked, up to some number.

Bugs or Edge Cases it Helps Avoid

Currently it seems one has to manually implement whole logic how to handle interaction between multiple snackbar messages.

EDIT: I have made a NPM package with this functionality @tozd/vue-snackbar-queue.

@nekosaur nekosaur added the T: feature A new feature label Oct 31, 2017
@nekosaur
Copy link
Member

The reason for not currently supporting multiple snackbar messages is that MD spec specifies that only one snackbar should be displayed at a time.

However, several people have made similar requests previously. And I think there is some merit to at least include basic support for displaying consecutive snackbar messages. Not a fan of providing a global queue in vuetify though.

Will leave this open for discussion.

@tafel
Copy link

tafel commented Oct 31, 2017

One could create a component (say SnackDisplayer) and, using Vuex and stores, displaying the snackbar outside of any component (well... except the 1st level component containing the v-app tag).

I tried this with a quite successful result. Here is a fiddle that demonstrate this:
http://jsfiddle.net/13uc6mu5/

With this configuration (check fiddle source), one can call this.$store.commit('msg/set', { message: 'hello' }) in any component and the snack pop up.

One thing that can be improved is indeed the possibility to have consecutive snackbars.

@KaelWD
Copy link
Member

KaelWD commented Oct 31, 2017

I think it'd make sense to allow the v-snackbar to handle the message queue internally.

@mitar
Copy link
Contributor Author

mitar commented Oct 31, 2017

The reason for not currently supporting multiple snackbar messages is that MD spec specifies that only one snackbar should be displayed at a time.

So one way could be that they are queued and displayed one after another. But optional stacking would be cool as well. (Or maybe one message, but with a badge in the corner showing that there are many other pending behind this one.)

Is there some other more MD-approved approach to display multiple messages to the user?

@peluprvi
Copy link
Contributor

peluprvi commented Nov 1, 2017

The way Google applies the MD specs is closing the opened snackbar/toast when a new one is dispatched. You can see it in the new Google Calendar web version (more info), Angular Material and Angular Material2.

@Phlow2001
Copy link
Contributor

I wrote a little mixin to provide a snackbar queue, it can be easily incorporated in your app: https://codepen.io/jayblanchard/pen/yoxPRY

@mitar
Copy link
Contributor Author

mitar commented Nov 3, 2017

@Phlow2001, oh so easy. Thanks!

I would love to also have a badge on the side of the snackbar, to show how many pending snackbars are in the queue, but it is not as easy as I expected: #2392

@pschaub
Copy link

pschaub commented Dec 8, 2017

@Phlow2001 But your mixin does not align to the MD specs that @peluprvi mentioned.

The way Google applies the MD specs is closing the opened snackbar/toast when a new one is dispatched.

@manuel-di-iorio
Copy link

@pschaub Based on the code of @Phlow2001, this code should aligns with the MD specs:

addNotification(text) {
    this.notification = false
      
    setTimeout(() => {
        this.notificationText = text
        this.notification = true
    }, 250)
}

No snackbar queue, a new notification will replace the current one.

Codepen: https://codepen.io/manuel-di-iorio/pen/YeyVMb
MD example: https://material.angular.io/components/snack-bar/examples

@daniandl
Copy link
Contributor

daniandl commented Mar 3, 2018

I've been experimenting around with a queue system for the dialogs. I also want to spawn them programatically so here is what I have so far. Let it be an inspiration to whatever feature you guys are building, I'm not confident enough in it to PR it.

index.js

import Vue from 'vue'
import Toast from './Toast'

let queue = []
let showing = false

export { Toast }
export default {
  open(params) {
    if (!params.text) return console.error('[toast] no text supplied')
    if (!params.type) params.type = 'info'

    let propsData = {
      title: params.title,
      text: params.text,
      type: params.type
    }

    let defaultOptions = {
      color: params.type || 'info',
      closeable: true,
      autoHeight: true,
      timeout: 1000,
      multiLine: !!params.title || params.text.length > 80
    }

    params.options = Object.assign(defaultOptions, params.options)
    propsData.options = params.options

    // push into queue
    queue.push(propsData)
    processQueue()
  }
}

function processQueue() {
  if (queue.length < 1) return
  if (showing) return

  console.log(queue)

  let nextInLine = queue[0]
  spawn(nextInLine)
  showing = true

  queue.shift()
}

function spawn(propsData) {
  const ToastComponent = Vue.extend(Toast)
  return new ToastComponent({
    el: document.createElement('div'),
    propsData,
    onClose: function() {
      showing = false
      processQueue()
    }
  })
}

toast.vue

<template>
  <v-snackbar v-model="open" v-bind="options">
    <div class="ctn">
      <div class="title mb-2" v-if="title">{{title}}</div>
      <div class="txt">{{text}}</div>
    </div>
    <v-btn v-if="options.closeable" flat icon @click.native="open = false">
      <fai :icon="['fal', 'times']" size="lg"></fai>
    </v-btn>
  </v-snackbar>
</template>

<script>
/* TODO */

export default {
  name: 'toast',
  props: {
    title: String,
    text: String,
    type: String,
    options: Object,
  },
  data() {
    return {
      open: false,
    }
  },
  watch: {
    open: function(val) {
      if (!val) {
        this.close()
      }
    },
  },
  beforeMount() {
    document.querySelector('#app').appendChild(this.$el)
  },
  mounted() {
    this.open = true
  },
  methods: {
    close() {
      if (this.open) this.open = false
      setTimeout(() => {
        this.$options.onClose()
        this.$destroy()
        removeElement(this.$el)
      }, 700) // wait for close animation
    },
  },
}

function removeElement(el) {
  if (typeof el.remove !== 'undefined') {
    el.remove()
  } else {
    el.parentNode.removeChild(el)
  }
}
</script>

main.js

...
import Toast from './components/toast'
Vue.prototype.$toast = Toast
...

Then just use this.$toast.open({text: 'test'}) and it should show a dialog.
Use at your own risk.

@juanpa669
Copy link
Contributor

Another Mixin that somebody gave me on the discord channel, sorry I don't remember who

export default {
  data: () => ({
    toast: {
      text: 'I am a Snackbar !',
      color: 'success',
      timeout: 5000,
      top: true,
      bottom: false,
      right: false,
      left: false,
      multiline: false
    },
    notificationQueue: [],
    notification: false
  }),
  computed: {
    hasNotificationsPending () {
      return this.notificationQueue.length > 0
    }
  },
  watch: {
    notification () {
      if (!this.notification && this.hasNotificationsPending) {
        this.toast = this.notificationQueue.shift()
        this.$nextTick(() => { this.notification = true })
      }
    }
  },
  methods: {
    addNotification (toast) {
      if (typeof toast !== 'object') return
      this.notificationQueue.push(toast)

      if (!this.notification) {
        this.toast = this.notificationQueue.shift()
        this.notification = true
      }
    },
    makeToast (text, color = 'info', timeout = 6000, top = true, bottom = false, right = false, left = false, multiline = false, vertical = false) {
      return {
        text,
        color,
        timeout,
        top,
        bottom,
        right,
        left,
        multiline,
        vertical
      }
    }
  }
}

Usage:

this.addNotification({text: 'Some Text', color: 'danger', timeout: 5000})

this.addNotification({text: 'Some Text', color: 'danger', timeout: 5000, top: false, bottom: true, multiline: true})

@ankitsinghaniyaz
Copy link

I have used buefy before and it has a neat implementation for this. There is no need for the component. The $snackbar is available on the Vue instance. This saves me a lot of hassle about putting the data into state first and then using it to display the snack bar. They do something like this:

this.$snackbar.open(`Default, positioned bottom-right with a green 'OK' button`)

This can be triggered by any network call, vuex action, etc.

Soruce: https://buefy.github.io/#/documentation/snackbar

@daniandl
Copy link
Contributor

@ankitsinghaniyaz my example is inspired by buefy, you can see the same index structure

@kasvith
Copy link

kasvith commented Mar 21, 2018

Agree to @ankitsinghaniyaz buefy has a nice implementation for this

@Flamenco
Copy link
Contributor

A couple of months ago I added a component to NPM that handles this and more.
https://www.npmjs.com/package/snackbarstack

Here's a demo
https://codepen.io/Flamenco/full/ZoRvLw/

@aldarund
Copy link
Contributor

@Flamenco no github repo for it?

@Flamenco
Copy link
Contributor

@aldarund It's using a few of our internal libraries I can't open source at this moment; The binaries are free to distribute however.

@raphaelsoul
Copy link

@Flamenco thanks, pretty nice. But hard to customize it. Such as change position, color etc.

@Flamenco
Copy link
Contributor

@raphaelsoul Some of those are on our wish list in the todo section of NPM. If I can get to it, I will post a github project to collect feature requests.

What kind of API would you suggest for change position?

@shershen08
Copy link

@Flamenco cool demo, thanks for nice job!
Is it not on GihHub? I'd love to fork it and add a stacking feature

@Flamenco
Copy link
Contributor

@shershen08 This is not open source yet. We have a collection of Vuetify utilities that will most likely be released under a CC license.

@delueg
Copy link

delueg commented Dec 12, 2018

I did a more or less simple CSS Hack i want to share if anybody feels like going in the same direction.

WARNING this will brake vuetify default styling and possibility to change positioning the default way

HTLM

<div id="snackbar">
                    <v-snackbar
                            v-for="(snackbar, index) in snackbars"
                            v-model="snackbar.show"
                            :multi-line="true"
                            :right="true"
                            :timeout=6000
                            :top="true"
                            :color="snackbar.color"
                    >
                        {{ snackbar.text }}
                        <v-btn
                                flat
                                @click="hideSnackbar(index)"
                        >
                            <v-icon>close</v-icon>
                        </v-btn>
                    </v-snackbar>
                </div>

SCSS

#snackbar{
        position: fixed;
        top: 0;
        right: 0;
        z-index: 9999999999;
        display: flex;
        width: 100vw;
        flex-direction: column-reverse;
        align-items: flex-end;
        justify-content: space-around;
        div{
            position: relative !important;
            margin-bottom: 8px;
        }
    }

//vuex-store

snackbars : [],

//vuex-mutations

showSnackbar ( state, value) {
            state.snackbars.push( value )
        },
        hideSnackbar ( state, index ) {
            state.snackbars[index].show = false;
        },

Global Access

Vue.mixin( {
    methods : {
        showSnackbar ( color, text  ) {
            this.$store.commit( 'showSnackbar', {
                'show' : true,
                'color' : color,
                'text' : text
            } )
        },
        hideSnackbar ( index ) {
            this.$store.commit( 'hideSnackbar', index )
        }
    }
} )

//Usage in component

this.showSnackbar('info','bla')

@websitevirtuoso
Copy link
Contributor

A long time ago I created package with do a lot and working as plugin.
https:/websitevirtuoso/vue3-v-snackbars
I use this in production

@wobsoriano
Copy link

Published another stackable toast component if anyone's interested - https://vuetify-sonner.vercel.app

Screenshot 2023-07-07 232504

@alihdev
Copy link

alihdev commented Jul 13, 2023

To show all the snackbars in a stack way, please check : https:/alihdev/vuetify-helper/blob/main/snackbars-stack-way.md

snackbars-stack-way

@shabab477
Copy link

Having this as an internal config would be really helpful.

@johnleider
Copy link
Member

We are planning on experimenting with this now that 6c9eb7a is in.

@hi-reeve
Copy link

hi-reeve commented Oct 9, 2023

any update on this?

@johnleider
Copy link
Member

any update on this?

Not planning work on this until after v3.4 (November)

@KaelWD KaelWD assigned KaelWD and unassigned johnleider Nov 24, 2023
@KaelWD KaelWD modified the milestones: v3.x.x, v3.6.0 (Nebula) Jan 30, 2024
johnleider added a commit that referenced this issue Apr 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment