Skip to content

Instantly share code, notes, and snippets.

@eolant
Last active July 29, 2024 18:14
Show Gist options
  • Save eolant/ba0f8a5c9135d1a146e1db575276177d to your computer and use it in GitHub Desktop.
Save eolant/ba0f8a5c9135d1a146e1db575276177d to your computer and use it in GitHub Desktop.
Vuetify Confirm Dialog component that can be used locally or globally
<template>
<v-dialog v-model="dialog" :max-width="options.width" :style="{ zIndex: options.zIndex }" @keydown.esc="cancel">
<v-card>
<v-toolbar dark :color="options.color" dense flat>
<v-toolbar-title class="white--text">{{ title }}</v-toolbar-title>
</v-toolbar>
<v-card-text v-show="!!message" class="pa-4">{{ message }}</v-card-text>
<v-card-actions class="pt-0">
<v-spacer></v-spacer>
<v-btn color="primary darken-1" text @click.native="agree">Yes</v-btn>
<v-btn color="grey" text @click.native="cancel">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
/**
* Vuetify Confirm Dialog component
*
* Insert component where you want to use it:
* <confirm ref="confirm"></confirm>
*
* Call it:
* this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
* Or use await:
* if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
* // yes
* }
* else {
* // cancel
* }
*
* Alternatively you can place it in main App component and access it globally via this.$root.$confirm
* <template>
* <v-app>
* ...
* <confirm ref="confirm"></confirm>
* </v-app>
* </template>
*
* mounted() {
* this.$root.$confirm = this.$refs.confirm.open
* }
*/
export default {
data: () => ({
dialog: false,
resolve: null,
reject: null,
message: null,
title: null,
options: {
color: 'primary',
width: 290,
zIndex: 200
}
}),
methods: {
open(title, message, options) {
this.dialog = true
this.title = title
this.message = message
this.options = Object.assign(this.options, options)
return new Promise((resolve, reject) => {
this.resolve = resolve
this.reject = reject
})
},
agree() {
this.resolve(true)
this.dialog = false
},
cancel() {
this.resolve(false)
this.dialog = false
}
}
}
</script>
@rom-23
Copy link

rom-23 commented Oct 10, 2019

Thanks , good job ;)

@edrichhans
Copy link

Thank you for this!! <3 <3 <3

@i-defranca
Copy link

Maybe it would be useful to add an option without the cancel button, when it's a simple message with an ok button. But I think for most users the vuetify snackbar it's enough.
And thank you, of course! I was having trouble making a global component and this code helped me :)

@eolant
Copy link
Author

eolant commented Nov 1, 2019

@i-defranca since this is not a library you can do any modifications you want :)

@mazdak78
Copy link

Fantastic. Thank you.

@Kaspler
Copy link

Kaspler commented Dec 4, 2019

Small change to return "false" when the dialog is closed by clicking outside:

<template>
  <v-dialog
    v-model="show"
    :max-width="options.width"
    :style="{ zIndex: options.zIndex }"
    @keydown.esc="cancel"
  >
    <v-card>
      <v-toolbar :color="options.color" dark dense flat>
        <v-toolbar-title class="white--text">{{ title }}</v-toolbar-title>
      </v-toolbar>
      <v-card-text v-show="!!message" class="pa-4">{{ message }}</v-card-text>
      <v-card-actions class="pt-0">
        <v-spacer></v-spacer>
        <v-btn @click.native="agree" color="primary darken-1" text>Yes</v-btn>
        <v-btn @click.native="cancel" color="grey" text>Cancel</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script>
/**
 * Vuetify Confirm Dialog component
 *
 * Insert component where you want to use it:
 * <confirm ref="confirm"></confirm>
 *
 * Call it:
 * this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
 * Or use await:
 * if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
 *   // yes
 * }
 * else {
 *   // cancel
 * }
 *
 * Alternatively you can place it in main App component and access it globally via this.$root.$confirm
 * <template>
 *   <v-app>
 *     ...
 *     <confirm ref="confirm"></confirm>
 *   </v-app>
 * </template>
 *
 * mounted() {
 *   this.$root.$confirm = this.$refs.confirm.open
 * }
 */
export default {
  data: () => ({
    dialog: false,
    resolve: null,
    reject: null,
    message: null,
    title: null,
    options: {
      color: 'primary',
      width: 290,
      zIndex: 200
    }
  }),
  computed: {
    show: {
      get() {
        return this.dialog
      },
      set(value) {
        this.dialog = value
        if (value === false) {
          this.cancel()
        }
      }
    }
  },
  methods: {
    open(title, message, options) {
      this.dialog = true
      this.title = title
      this.message = message
      this.options = Object.assign(this.options, options)
      return new Promise((resolve, reject) => {
        this.resolve = resolve
        this.reject = reject
      })
    },
    agree() {
      this.resolve(true)
      this.dialog = false
    },
    cancel() {
      this.resolve(false)
      this.dialog = false
    }
  }
}
</script>

Now, the dialog itself is bound to a computed value "show", which in turn is bound to the "dialog" property. When the value of "show "is set to false - it will trigger the "close()" method.

@NavidMitchell
Copy link

Nice Work!

@guilherme-miranda
Copy link

Tks!

@shehanrangana
Copy link

Nice work !

@timyourivh
Copy link

Learned a lot from this thank you!

@bekaku
Copy link

bekaku commented May 22, 2020

composition-api Version

Confirm.vue

import { defineComponent, reactive, toRefs } from "@vue/composition-api";
export default defineComponent({
  setup() {
    const state = reactive({
      dialog: false,
      resolve: null,
      reject: null,
      message: null,
      title: null,
      options: {
        color: "primary",
        width: 290,
        zIndex: 200
      }
    });
    const open = (title, message, options) => {
      state.dialog = true;
      state.title = title;
      state.message = message;
      state.options = Object.assign(state.options, options);
      return new Promise((resolve, reject) => {
        state.resolve = resolve;
        state.reject = reject;
      });
    };

    const agree = () => {
      console.log("confirm");
      state.resolve(true);
      state.dialog = false;
    };

    const cancel = () => {
      console.log("cancle");
      state.resolve(false);
      state.dialog = false;
    };

    return {
      ...toRefs(state),
      open,
      agree,
      cancel
    };
  }
});
</script>

call it :
Page.vue

<template>
     <div>
              <v-btn
                icon
                color="red"
                @click="openConfirm "
              >
                <v-icon>mdi-trash-can-outline</v-icon>
              </v-btn>
           <confirm ref="veeConfirmRef"></confirm>
     </div>
</template>
<script>
import { onMounted } from "@vue/composition-api";
export default {
  components: {
    Confirm: () => import("@/components/Confirm")
  },
 setup(props, { refs }) {
 
    onMounted(() => {
      console.log('page onMounted')
    });
  const openConfirm = ()=> {
       refs.veeConfirmRef
        .open('Delete', 'Are you sure?', { color: "error" })
        .then(confirm => {
          console.log("onBeforeDeleteItem confirm : " + confirm);
        });

    return{
       openConfirm 
    }
  }
};
</script>

@twoco
Copy link

twoco commented May 25, 2020

For TypeScript to prevent type errors.

this.$root.$refs.confirm = this.$refs.confirm;
// or ... to avoid type errors.
this.$root.$data.confirm = this.$refs.confirm;

Maybe this is the more correct way, isn't? I'm new in Vue(tify). But the $root $refs exists as type.

(property) Vue.$refs: {
    [key: string]: Vue | Element | Vue[] | Element[];
}

You could also use a shared class ...

@eliyahuKriel
Copy link

thank you very much! i search for this all over the web!
how can i call this.$root.$confirm from VUEX ? i want to active the modal from my store.. is a way to get there through JS file?

@eolant
Copy link
Author

eolant commented Jun 8, 2020

@eliyahuKriel you can try importing app instance.

@eliyahuKriel
Copy link

eliyahuKriel commented Jun 8, 2020

thanks, i am tring but the modal just dont open..
export const SET_CONFIRM_MODAL = (state, modalData) => { App.components.Confirm.methods.openModal(modalData) };

should i access other way to the openModal method?

@eolant
Copy link
Author

eolant commented Jun 8, 2020

@eliyahuKriel hmm, not sure about your code, but you should use an instance app:

export const app = new Vue({
  router,
  store,
  render: h => h(require('$comp/App'))
}).$mount('#app');

@eliyahuKriel
Copy link

God bless you my friend, you have helped me very much

@jmacias54
Copy link

great ...!!!

@Cerceis
Copy link

Cerceis commented Nov 18, 2020

Thanks, this was helpful.

In the description to use this globally, I think this part:

mounted() {
   *   this.$root.$confirm = this.$refs.confirm.open;`

Should actually be this:

mounted() {
   *   this.$root.$confirm = this.$refs.confirm;

Along with this post.
When using it as global component(registered in App.vue)
I need to use *await this.$root.$confirm.open('Delete', 'Are you sure?', { color: 'red' })
instead of *await this.$refs.$confirm.open('Delete', 'Are you sure?', { color: 'red' })

@activeliang
Copy link

activeliang commented Nov 28, 2020

How to use in general JS files or use it in global
Called in custom modules, such as router.js, api.js, custom.js

My solution:
Create a new file: confirm.js

import Confirm from './confirm.vue' 
import Vue from 'vue'

const instance = new (Vue.extend(Confirm))()

document.addEventListener('DOMContentLoaded',function(){
  instance.$vuetify = { breakpoint: {}}
  instance.$mount(document.createElement('div'))
})

export default instance

Then in main.js:

import Confirm from './cofirm.js'
Vue.prototype.$confirm = Confirm

Then in the js file you want to call:

import Confirm from './cofirm.js'
Confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})

perfect!

It is worth noting:

  1. The v-dialog component must provide the dark parameter to use the dark theme
  2. The purpose of instance.$vuetify = { breakpoint: {}} in the above example is to remove the error message Error in callback for watcher "isActive": "TypeError: Cannot read property 'smAndDown' of undefined

It could be called both in Vue instance context:

this.$confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => { console.log('close') })

@atreya2011
Copy link

atreya2011 commented Jan 7, 2021

Here is the TypeScript version

import { defineComponent, reactive, toRefs } from "@vue/composition-api";

interface Options {
  color: string;
  width: number;
  zIndex: number;
}

export default defineComponent({
  setup() {
    const state = reactive({
      dialog: false,
      resolve: (val: boolean) => {},
      reject: (val: boolean) => {},
      message: "",
      title: "",
      options: {
        color: "primary",
        width: 290,
        zIndex: 200,
      },
    });

    const open = (title: string, message: string, options: Options) => {
      state.dialog = true;
      state.title = title;
      state.message = message;
      state.options = Object.assign(state.options, options);
      return new Promise<boolean>((resolve, reject) => {
        state.resolve = resolve;
        state.reject = reject;
      });
    };

    const agree = () => {
      console.log("confirm");
      state.resolve(true);
      state.dialog = false;
    };

    const cancel = () => {
      console.log("cancel");
      state.resolve(true);
      state.dialog = false;
    };

    return {
      ...toRefs(state),
      open,
      agree,
      cancel,
    };
  },
});

Call it this way using TypeScript:

<template>
  <div>
    <v-btn icon color="red" @click="openConfirm">
      <v-icon>mdi-trash-can-outline</v-icon>
    </v-btn>
    <confirm ref="veeConfirmRef"></confirm>
  </div>
</template>
<script lang="ts">
import { onMounted, ref } from "@vue/composition-api";
import Confirm from "@/components/Confirm.vue"

export default {
  components: {
    Confirm,
  },

  setup() {
    const veeConfirmRef = ref<InstanceType<typeof Confirm>>();

    onMounted(() => {
      console.log('page onMounted')
    });

    const openConfirm = ()=> {
      veeConfirmRef
        .open('Delete', 'Are you sure?', { color: "error" })
        .then(confirm => {
          console.log("onBeforeDeleteItem confirm : " + confirm);
        });
    };

    return{
      openConfirm,
      veeConfirmRef,
    }
  }
};
</script>

@StarWar
Copy link

StarWar commented Apr 3, 2021

I'm getting the following error after upgrading some dependencies i.e nuxtjs.

TypeError: Cannot read property 'open' of undefined
    at VueComponent.mounted (default.vue?ec86:160)
    at invokeWithErrorHandling (vue.runtime.esm.js?2b0e:1854)
    at callHook (vue.runtime.esm.js?2b0e:4219)
    at Object.insert (vue.runtime.esm.js?2b0e:3139)
    at invokeInsertHook (vue.runtime.esm.js?2b0e:6346)
    at Vue.patch [as __patch__] (vue.runtime.esm.js?2b0e:6565)
    at Vue._update (vue.runtime.esm.js?2b0e:3945)
    at Vue.updateComponent (vue.runtime.esm.js?2b0e:4060)
    at Watcher.get (vue.runtime.esm.js?2b0e:4479)
    at new Watcher (vue.runtime.esm.js?2b0e:4468)
    ```
    
    The error is at following lines
    ```
    mounted() {
    this.$root.$confirm = this.$refs.confirm.open
    this.$root.$toast = this.$refs.toast.open
  },

@shubh47-dddd
Copy link

this.$root.$confirm = this.$refs.confirm.open

Having same issue. How can we use this in nuxt globally. something like this.$root.$ref.dialog.open()

@jaysonpotter
Copy link

Just to add to the comments .. I registered this component globally based off of the Vue 2 documentation and use it all over vs adding it to the App.vue. This way I was able to use it as initially described.

// in your main.js before you instantiate your new Vue({}) application:
import ConfirmDialog from "@/components/shared/ConfirmDialog"
Vue.component('ConfirmDialog', ConfirmDialog)

// in <template>:
// <ConfirmDialog ref="confirm"></ConfirmDialog>

// Call it:
this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' }).then((confirm) => {})
// Or use await:
if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
  // yes
} else {
  // cancel
}

@wobsoriano
Copy link

wobsoriano commented Jul 18, 2021

If you are using the Composition API plugin, you can use provide/inject plus a useDialog composable that injects the show dialog function. Cleanest and TypeScript friendly!

Here's how you would call it:

<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api';
import { useDialog } from '~/composables';

export default defineComponent({
  setup() {
    const createConfirmDialog = useDialog();

    const handleDelete = async () => {
      try {
        const shouldProceed = await createConfirmDialog(
          'Confirm',
          'Delete this post?',
          { width: 300 }
        );
      } catch (_e) {}
    };

    return {
      handleDelete
    };
  },
});
</script>

Full implementation here: https://gist.github.com/wobsoriano/1e63708d7771c34f835c0f6e3c5e731a

@jtgasper3
Copy link

jtgasper3 commented Sep 5, 2021

Standing on the shoulders of the giants before me, I was able to get a global confirmation function working with Nuxt.js:

  1. In the layout component where the dialog is place:

    <app-confirmation-dialog ref="confirm" />
    ...
      //because I'm using Class based components I used an `@Ref()` shorthand, but using $refs in the RH side of the assignment below should work too:
     @Ref()
      readonly confirm!: AppConfirmationDialog
    ...
      // the real magic??
       mounted(): void {
        this.$nuxt.$root.$confirm = this.confirm.open
      }
  2. Its usage in other components:

    await this.$root.$confirm(
            'Skip Day',
            'Are you sure you want to toggle the Skip Day status?',
            {
              color: 'red',
            })
  3. Most likely because I am using Typescript I had to add a Nuxt plugin with:

    import { Options } from '@/types'
    
    declare module 'vue/types/vue' {
      interface Vue {
        $confirm(title: string, message: string, options: Options): Promise<boolean>
      }
    }
  4. and I had a type defined in a file shared between the plugin and the other components:

    export interface Options {
      color?: string
      width?: number
      zIndex?: number
    }

@julisch94
Copy link

Why use @click.native when @click seems to do the trick? Is there any scenario when a simple @click is not enough?

@activeliang
Copy link

If you need to implement it on vue3+vuetify3, you can refer to this article https://juejin.cn/post/7102240700774744095

@mepanko91
Copy link

mepanko91 commented Feb 22, 2024

<v-dialog
  v-model="dialog"
  @update:model-value="atDialogUpdate"
>

Add this function inside confirm dialog component so it will resolve with false if dialog is closed by clicking outside

function atDialogUpdate(val) {
  if (!val) {
    resolve(false);
  }
}

@pkhuji
Copy link

pkhuji commented Mar 23, 2024

If using in multiple places then confirmDialog component can be placed in root #app component like this

<div id='#app'>
  <confirmDialog :ref='store.setConfirmDialogRef' />
</div>

then in pinia store or any shared state

state: () => ({
  confirmDialogRef: null,
}),
actions: {
  setConfirmDialogRef(val) {
    this.confirmDialogRef = val;
  },
}

The store.setConfirmDialogRef method will update ref in store.
then in any function

async function submit() {
  if (!await store.confirmDialogRef.open(
    "You sure?",
    "Positive?",
  )) {
    return;
  }
  // axios.post
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment