I have a global confirm modal ponent that is registered on my default layout file, I would then try to access it from my pages/index.vue but the calling this.$refs there would just return an empty object. Placing the modal ponent in my pages/index.vue will work but it will defeat the purpose of my global confirm modal.
layouts/default.vue
<template lang="pug">
v-app(v-if="show")
v-main
transition
nuxt
confirm(ref='confirm')
</template>
<script>
import confirm from '~/ponents/confirm.vue'
export default {
ponents: { confirm },
data: () => ({
show: false
}),
async created() {
const isAuth = await this.$store.dispatch("checkAuth")
if (!isAuth) return this.$router.push("/login")
this.show = true
}
}
</script>
ponents/confirm.vue
<template>
<v-dialog v-model="dialog" :max-width="options.width" @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">{{ message }}</v-card-text>
<v-card-actions class="pt-0">
<v-spacer></v-spacer>
<v-btn color="primary darken-1" @click.native="agree">Yes</v-btn>
<v-btn color="grey" @click.native="cancel">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
data: () => ({
dialog: false,
resolve: null,
reject: null,
message: null,
title: null,
options: {
color: 'primary',
width: 290
}
}),
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>
I would then like to call it from my pages/index.vue like this (it would work if the ref is here but I would like a global confirm modal)
methods: {
async openConfirm() {
console.log("openConfirm")
if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
console.log('--yes')
}else{
console.log('--no')
}
},
I have a global confirm modal ponent that is registered on my default layout file, I would then try to access it from my pages/index.vue but the calling this.$refs there would just return an empty object. Placing the modal ponent in my pages/index.vue will work but it will defeat the purpose of my global confirm modal.
layouts/default.vue
<template lang="pug">
v-app(v-if="show")
v-main
transition
nuxt
confirm(ref='confirm')
</template>
<script>
import confirm from '~/ponents/confirm.vue'
export default {
ponents: { confirm },
data: () => ({
show: false
}),
async created() {
const isAuth = await this.$store.dispatch("checkAuth")
if (!isAuth) return this.$router.push("/login")
this.show = true
}
}
</script>
ponents/confirm.vue
<template>
<v-dialog v-model="dialog" :max-width="options.width" @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">{{ message }}</v-card-text>
<v-card-actions class="pt-0">
<v-spacer></v-spacer>
<v-btn color="primary darken-1" @click.native="agree">Yes</v-btn>
<v-btn color="grey" @click.native="cancel">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
data: () => ({
dialog: false,
resolve: null,
reject: null,
message: null,
title: null,
options: {
color: 'primary',
width: 290
}
}),
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>
I would then like to call it from my pages/index.vue like this (it would work if the ref is here but I would like a global confirm modal)
methods: {
async openConfirm() {
console.log("openConfirm")
if (await this.$refs.confirm.open('Delete', 'Are you sure?', { color: 'red' })) {
console.log('--yes')
}else{
console.log('--no')
}
},
Share
Improve this question
asked Aug 22, 2020 at 19:26
obliviousfellaobliviousfella
4451 gold badge11 silver badges22 bronze badges
1
- I think this may solve your problem - access-refs-from-other-ponents-not-in-the-same-level-as-current-ponent – sugars Commented Aug 23, 2020 at 2:26
2 Answers
Reset to default 3The short answer is: Don't abuse $ref like that. Ultimately, it will just lead to anti-patterns wrapped in anti-patterns.
More detailed answer related to the exact task you're trying to acplish: I've tackled this exact same thing (global, promise based confirmation dialogs) on a few Vue projects now, and this is what has worked really well so far:
- Setup the confirm dialog as its own stand-alone 'module' so you can add it to main.js with two lines:
import ConfirmModule from './modules/confirm';
Vue.use(ConfirmModule);
(side point: there's a couple other of these 'global modular ponents' like alerts, etc...)
- Use a JS file to orchestrate the setup process, the promise management, and the ponent instantiation. For example:
import vuetify from '@/plugins/vuetify';
import confirmDialog from './confirm-dialog.vue';
export default {
install(Vue) {
const $confirm = (title, text, options) => {
const promise = new Promise((resolve, reject) => {
try {
let dlg = true;
const props = {
title, text, options, dlg,
};
const on = { };
const p = new Vue({
vuetify,
render: (h) => h(confirmDialog, { props, on }),
});
on.confirmed = (val) => {
dlg = false;
resolve(val);
window.setTimeout(() => p.$destroy(), 100);
};
p.$mount();
document.getElementById('app').appendChild(p.$el);
} catch (err) {
reject(err);
}
});
return promise;
};
Vue.prototype.$confirm = $confirm;
},
};
Mount it to the Vue.prototype, this way you can use it from any ponent in your app simply by calling:
this.$confirm(...)
When you build your Vue ponent (confirm-dialog.vue) you need only one-way bind your props for title, text and options, one-way bind the dlg prop to the dialog, or else setup a two-way binding through a puted property with a getter and setter... either way...
emit a "confirmed" event with
true
if the user confirms. So, from the confirm-dialog.vue ponent:this.$emit('confirmed', true);
If they dismiss the dialog, or click 'no' then emit false so that the promise doesn't hang around:
this.$emit('confirmed', false);
Now, from any ponent, you can use it like so:
methods: {
confirmTheThing() {
this.$confirm('Do the thing', 'Are you really sure?', { color: 'red' })
.then(confirmed => {
if (confirmed) {
console.log('Well OK then!');
} else {
console.log('Eh, maybe next time...');
}
});
}
}
In nuxt project's default layout, put the ponent, say Confirm
like below:
<v-main>
<v-container fluid>
<nuxt />
<Confirm ref="confirm" />
</v-container>
</v-main>
Then the ponent's open
method can be used as below:
const confirmed = await this.$root.$children[2].$refs.confirm.open(...)
if (!confirmed) {
return // user cancelled, stop here
}
// user confirmed, proceed
Tricky thing was how to find the ponent contained in the layout. $root.$children[2]
part seemed working in development phase but once deployed, it had to be $root.$children[1]
.
So I ended up doing below:
// assume default.vue has data called 'COPYRIGHT_TEXT'
const child = this.$root.$children.find(x => x.COPYRIGHT_TEXT)
if (child) {
const confirm = child.$refs.confirm
if (confirm) {
return await confirm.open(...)
}
}
return false
Background: my project is in production but came up with a new requirement to get confirmation before any save in particular mode. I could use one-way event bus
, but to do that, the rest of codes after confirmation would have to be refactored to be passed as a callback in every saving place.
[31Oct22] Meanwhile the project migrated to position api, ended up to use store
's getter/setter to share Confirm
ponent's ref to avoid hacky way of finding ref as above:
// default.vue stores a ref
store.setRefGetConfirm(confirm)
// anywhere it needs to confirm
const confirmed = await store.refGetConfirm.open()