'Vue 3/Quasar: Handling opening and closing of modals
I have two modals:
One is a Sign Up modal which takes in information of an existing user. Another modal which allows an existing user to login.
The only way to get to the Login modal is through the Signup modal.
But what I would like to do is, if the use wants to open the Login, I would like to close the Sign up modal first.
Right now that seems impossible with my setup. With the code below (nextTick), it does not work and closes both modals... despite there being a unique v-model for each modal.
Sign up Modal
<template>
<AVModal
:title="$t('signup.create_an_account')"
:button-text="$t('signup.button_text')"
classes="hide-icon q-mt-sm"
modal-style="width: 350px"
v-model="modal"
>
<q-form
@submit="signUp(user)"
class="row column fitq-gutter-md q-gutter-md"
>
<q-input
outlined
v-model="signup_user.username"
:label="$t('signup.name')"
>
<template v-slot:append>
<q-icon name="person" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="signup_user.email"
type="email"
:label="$t('signup.email')"
>
<template v-slot:append>
<q-icon name="email" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="signup_user.password"
type="password"
:label="$t('signup.password')"
>
<template v-slot:append>
<q-icon name="lock" color="grey" />
</template>
</q-input>
<q-checkbox
v-model="signup_user.privacy_policy"
color="secondary"
:label="$t('signup.privacy_policy')"
/>
<q-checkbox
v-model="signup_user.newsletter"
color="secondary"
:label="$t('signup.newsletter')"
/>
<q-btn color="primary" class="q-py-sm" type="submit">{{
$t("signup.get_started")
}}</q-btn>
</q-form>
<div class="row q-my-sm q-mt-md fill">
<AVLoginModal @on-open="handleOpen" />
</div>
<!-- <AVSeperator text="or" /> -->
<!-- <AVSocialMediaButtons class="q-mt-md" /> -->
</AVModal>
</template>
<script setup>
import { ref, nextTick } from "vue";
import AVModal from "../atoms/AVModal.vue";
// import AVSeperator from "../atoms/AVSeperator.vue";
// import AVSocialMediaButtons from "../atoms/AVSocialMediaButtons.vue";
import AVLoginModal from "./LoginModal.vue";
import { useAuth } from "../../composables/useAuth";
const modal = ref(false);
const handleOpen = () => {
nextTick(() => (modal.value = false));
};
const { signUp, signup_user } = useAuth();
</script>
Login Modal:
<template>
<AVModal
:title="$t('login.welcome_back')"
:button-text="$t('signup.login_instead')"
classes="hide-icon q-mt-sm fit"
color="blue"
modal-style="width: 350px"
v-model="modal"
@on-open="emit('on-open')"
>
<q-form
@submit="login(login_user)"
class="row column fitq-gutter-md q-gutter-md"
>
<q-input
outlined
v-model="login_user.email"
type="email"
:label="$t('signup.email')"
>
<template v-slot:append>
<q-icon name="email" color="grey" />
</template>
</q-input>
<q-input
outlined
v-model="login_user.password"
type="password"
:label="$t('signup.password')"
>
<template v-slot:append>
<q-icon name="lock" color="grey" />
</template>
</q-input>
<q-btn color="primary" class="q-py-sm" type="submit">{{
$t("login.login")
}}</q-btn>
</q-form>
<!-- <AVSeperator text="or" class="q-my-md" /> -->
<!-- <AVSocialMediaButtons class="q-mt-md" /> -->
</AVModal>
</template>
<script setup>
import { ref, defineEmits } from "vue";
import { useAuth } from "../../composables/useAuth";
import AVModal from "../atoms/AVModal.vue";
// import AVSeperator from "../atoms/AVSeperator.vue";
// import AVSocialMediaButtons from "../atoms/AVSocialMediaButtons.vue";
const modal = ref(false);
const emit = defineEmits(["on-open"]);
const { login_user, login } = useAuth();
</script>
Maybe my design is bad? Is there a more conventional way of creating modals in Quasar?
Here is my reuseable Modal component:
<template>
<q-btn
:color="color"
:align="align"
flat
@click="openModal"
:icon="icon"
:class="[classes]"
:style="{ width }"
>{{ buttonText }}</q-btn
>
<q-dialog v-model="modal" :persistent="persistent">
<q-card :style="modalStyle">
<q-card-section>
<div class="row justify-between">
<div>
<div class="text-h6">{{ title }}</div>
<div class="text-subtitle2">
<slot name="subtitle" />
</div>
</div>
<slot name="top-right" />
</div>
</q-card-section>
<q-card-section class="q-pt-none">
<slot />
</q-card-section>
<q-card-actions align="right" class="bg-white text-teal">
<slot name="actions" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup>
import { computed, defineProps, defineEmits } from "vue";
const props = defineProps({
icon: {
type: String,
default: null,
},
color: {
type: String,
default: "",
},
title: {
type: String,
default: "",
},
buttonText: {
type: String,
default: "Open Modal",
},
modalStyle: {
type: String,
default: "width: 900px; max-width: 80vw",
},
width: {
type: String,
default: "",
},
align: {
type: String,
default: "around",
},
classes: {
type: String,
default: "",
},
persistent: {
type: Boolean,
default: false,
},
modelValue: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["on-open", "update:modelValue"]);
const modal = computed({
get: () => props.modelValue,
set: (val) => emit("update:modelValue", val),
});
const openModal = () => {
modal.value = true;
emit("on-open");
};
</script>
<style lang="scss">
.hide-icon {
i.q-icon {
display: none;
}
}
</style>
Solution 1:[1]
You can use the Dialog plugin with a custom component. By doing that, you can not just correctly open nested dialogs, but also simplify the prop/event management. Instead of a re-usable modal(AVModal.vue), you would create a re-usable card/container. Then, use that card in your Dialog plugin custom components.
Here is what closing the signup modal would look like in both approaches:
// Signup Modal
// Current way to close the modal (which doesn't correctly work)
modal.value = false; // v-model="modal"
// Custom dialog component way
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
onDialogCancel();
Instead of a button and a q-dialog inside the component, you would have the custom dialog component in a separate file, then when the button is clicked, invoke the Dialog plugin like this:
Dialog.create({
component: LoginModal,
})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Yusuf Kandemir |
