-
-
Save eolant/ba0f8a5c9135d1a146e1db575276177d to your computer and use it in GitHub Desktop.
<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> |
Thank you for this!! <3 <3 <3
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 :)
@i-defranca since this is not a library you can do any modifications you want :)
Fantastic. Thank you.
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.
Nice Work!
Tks!
Nice work !
Learned a lot from this thank you!
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>
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 ...
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?
@eliyahuKriel you can try importing app
instance.
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?
@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');
God bless you my friend, you have helped me very much
great ...!!!
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' })
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:
- The v-dialog component must provide the
dark
parameter to use the dark theme - The purpose of
instance.$vuetify = { breakpoint: {}}
in the above example is to remove the error messageError 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') })
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>
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
},
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()
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
}
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
Standing on the shoulders of the giants before me, I was able to get a global confirm
ation function working with Nuxt.js:
-
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 }
-
Its usage in other components:
await this.$root.$confirm( 'Skip Day', 'Are you sure you want to toggle the Skip Day status?', { color: 'red', })
-
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> } }
-
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 }
Why use @click.native
when @click
seems to do the trick? Is there any scenario when a simple @click
is not enough?
If you need to implement it on vue3+vuetify3, you can refer to this article https://juejin.cn/post/7102240700774744095
<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);
}
}
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
}
Thanks , good job ;)