Vue 3/Quasar: Handling opening and closing of modals - javascript

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>

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,
})

Related

How to set parent component data from child component in vuejs

Below is the parent component and child component. I am trying to access tabs_value data in the parent component from the child component but it is returning as undefined.
this.$parent.tabs_value returns as undefined when I try to access it inside the run method in the child component.
Please help me find where I am going wrong? Below is the code
Parent Component
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<v-tabs v-model="tabs_value"
color="black"
centered
show-arrows
>
<v-toolbar-title>Custom</v-toolbar-title>
<v-spacer></v-spacer>
<v-tab href="#build">Build</v-tab>
<v-tab href="#run">Run</v-tab>
</v-tabs>
<v-tabs-items v-model="tabs_value">
<v-tab-item value="#build" id="build">
<Build ref="build_reports" />
</v-tab-item>
<v-tab-item value="#run" id="run">
<Run :reports="reports" ref="run_reports" />
</v-tab-item>
</v-tabs-items>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import Build from 'views/build.vue'
import Run from 'views/run.vue'
import URLs from 'views/routes'
export default {
components: {
Build,
Run
},
data: function() {
return {
tabs_value: 'build',
isLoaded: true,
reports: []
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.$axios.get(URLs.REPORTS_URL)
.then(response => {
this.reports = response.data
});
}
}
};
</script>
Child Component run.vue
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm12 lg12>
<div>
<v-card>
<div>
<v-data-table
:headers="headers"
:items="reports"
hide-default-footer
:mobile-breakpoint="0">
<template slot="item" slot-scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>
<div>
<v-tooltip attach left>
<template v-slot:activator="{ on, attrs }">
<a v-bind="attrs" v-on="on"
class="" href='javascript:void(0);'
#click="run(props.item)"><i small slot="activator" dark color="primary" class="fas fa-play"></i></a>
</template>
<span>Run</span>
</v-tooltip>
</div>
</td>
</tr>
</template>
<template slot="no-data" >
<v-alert id='no-data' :value="true" color="error" icon="warning">
No Reports Yet
</v-alert>
</template>
</v-data-table>
</div>
</v-card>
</div>
</v-flex>
</v-layout>
</div>
</template>
<script>
import URLs from 'views/routes'
export default {
props: ['reports'],
data: function() {
return {
headers: [
{ text: 'Name', value: 'name', sortable: false },
{ text: 'Actions', sortable: false }
],
}
},
methods: {
run(report) {
debugger
// this.$parent.tabs_value returns as undefined
}
}
}
</script>
you can use component events i.e $emit.
Below is example which will tell you how to use $emit. (https://vuejs.org/guide/components/events.html#emitting-and-listening-to-events)
Parent Component
<template>
<ChildComponent #updateTabsValue="updateTabsValue"/>
</template>
<script>
export default {
data(){
return {
tabsValue: 'tabs',
};
},
methods:{
updateTabsValue(val){
this.tabsValue = val;
}
},
}
</script>
Child Component
<template>
<button #click="$emit('updateTabsValue','newVal')"/>
</template>

Make comment specific to post

I rendered all of the posts using v-for, but now I want to add a comment section to each. I don't know how to make a connection between a comment and a post. As you can see in the code, the comment section renders for all post cards, but how can it be specific to each post? (I am not sure of the logic and how I can implement it).
<template>
<div v-for="item in userList" :key="item.uid">
<div class="row q-py-xs">
<q-card flat class="bg-grey-3 full-width user-card">
<q-card-section>
<div class="text-body1 q-ma-xs" id="postDtls">
{{ item.postDetails }}
</div>
<div class="row q-gutter-sm">
<q-chip square>
<q-avatar icon="home" color="amber-10" text-color="white" />
{{ item.postAddress }}
</q-chip>
</div>
</q-card-section>
<q-card-section> </q-card-section>
<q-card-section>
<q-input
outlined
id="cmInput"
v-model="cmOffer"
class="bg-grey-1"
color="amber-10"
label-slot
clearable
>
<template v-slot:label>
<span class="text-weight-bold text-deep-orange">write</span>
your comment
</template>
<template v-slot:append>
<q-btn
round
flat
icon="move_to_inbox"
v-on:click="submitComment()"
/>
</template>
</q-input>
<q-btn push dense class="full-width q-my-xs" color="amber-10" label="send message directly" />
</q-card-section>
</q-card>
</div>
</div>
</template>
<script>
import { ref } from "vue";
import { db } from "src/boot/firebase";
import { collection, query, where, getDocs } from "firebase/firestore";
import { doc, setDoc } from "firebase/firestore";
export default {
setup() {
return {
userList: ref([]),
cmList: ref([]),
idList: ref([]),
cmOffer: ref("")
};
},
async created() {
const querySnapshot = await getDocs(collection(db, "post"));
const postCm = await getDocs(collection(db, "post"));
querySnapshot.forEach(doc => {
let docData = doc.data();
this.userList.unshift(docData);
});
postCm.forEach(doc => {
let cmData = doc.data();
this.cmList.unshift(cmData);
});
},
};
</script>
You can create array of comments for every post, and then update them accordingly. Please take look at following snippet:
const { ref } = Vue
const app = Vue.createApp({
setup () {
const userList = ref([{uid: 1, postDetails: 'post 1', postAddress: '1', comments: ['comment 1', 'comment 2']}, {uid: 2, postDetails: 'post 2', postAddress: '2', comments: []}])
const cmOffer = ref([])
const submitComment = (id, i) => {
userList.value = userList.value.map(user => {
if(user.uid === id) {
user.comments.push(cmOffer.value[i])
}
return {...user}
})
cmOffer.value = []
}
return { userList, cmOffer, submitComment }
}
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.4.13/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
{{ userList }}
<div v-for="(item, idx) in userList" :key="item.uid">
<div class="row q-py-xs">
<q-card flat class="bg-grey-3 full-width user-card">
<q-card-section>
<div class="text-body1 q-ma-xs" id="postDtls">
{{ item.postDetails }}
</div>
<div class="row q-gutter-sm">
<q-chip square>
<q-avatar icon="home" color="amber-10" text-color="white" />
{{ item.postAddress }}
</q-chip>
</div>
</q-card-section>
<q-card-section>
<div class="text-body1 q-ma-xs" v-for="(comment, i) in item.comments" :key="i" >
{{ comment }}
</div>
</q-card-section>
<q-card-section>
<q-input outlined id="cmInput" v-model="cmOffer[idx]" class="bg-grey-1" color="amber-10" label-slot clearable>
<template v-slot:label>
<span class="text-weight-bold text-deep-orange">write</span>
your comment
</template>
<template v-slot:append>
<q-btn round flat icon="move_to_inbox" #click="submitComment(item.uid, idx)" />
</template>
</q-input>
<q-btn push dense class="full-width q-my-xs" color="amber-10" label="send message directly" #click="submitComment(item.uid, idx)" />
</q-card-section>
</q-card>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.4.13/dist/quasar.umd.prod.js"></script>

Quasar: drawerClick not working when I click one of menus

I would like to have responsive drawer, so tried to code in accordance with Quasar Docs, however, it was not resized properly when the screen size is being changed. In order to solve this, I used computed in the script. I think due to this computed, the function drawerClick not working.
When the drawer is on minimode, and one of menus are clicked, then the menu should be expanded, but the problem I have is it never expanded.
template
<template>
<q-drawer
v-model="drawer"
show-if-above
:mini="!drawer || miniState || miniStatus"
#click.capture="drawerClick"
:width="220"
:breakpoint="500"
bordered
:content-style="{ backgroundColor: '#f5f7f9' }"
id="side-menu"
>
<q-scroll-area class="fit">
<q-list padding>
<q-btn v-if="!miniState" flat left class="logo-btn">
<img src="~assets/os_logo.png" width="144px" height="24px" contain />
</q-btn>
<q-btn v-else flat left>
<img src="~assets/os_logo_no_text.png" width="24px" contain />
</q-btn>
<q-expansion-item
default-opened
v-for="(menu, index) in menus"
header-class="header-bg text-black"
expand-icon-class="text-gray"
>
<q-expansion-item
v-for="(sub, index) in menu.subMenus"
:key="index"
:label="sub.title"
expand-icon="none"
class="sub-content"
:to="{ name: sub.link }"
/>
</q-expansion-item>
</q-list>
</q-scroll-area>
</q-drawer>
</template>
script
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
name: 'SideMenu',
data() {
return {
drawer: false,
miniStatus: false,
switchToMini: 700
};
},
computed: {
miniState: function() {
return this.$q.screen.width < this.switchToMini;
}
},
methods: {
drawerClick(e) {
if (this.miniStatus) {
this.miniStatus = false;
e.stopPropagation();
}
}
}
});
</script>
I explained better here in this image
[1]: https://i.stack.imgur.com/PqYoc.png

I can't figure out how to make a modal 'settings button' component

I'm using Quasar VueJS and wish to make a button on my navbar that opens up a pop-up dialog settings panel. I'm going to be using this settings panel for stuff like dynamic theming, but that's a tad off-topic.
I'm currently ~SEVERELY~ struggling to figure out how to do this.
"layouts/MainLayout.vue"
<template>
<q-btn
unelevated
icon="settings"
label="Settings"
color="primary"
v-on:click="SetterUpper"
/>
</template>
<script>
import SetterUpper from "components/SetterUpper";
export default {
name: "MainLayout",
Component: {
SetterUpper
},
};
</script>
"components/SetterUpper.vue"
<template>
<q-dialog v-model="SetterUpper" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="settings" color="primary" text-color="white" />
<span class="q-ml-sm">Placeholder</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="Save" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script>
export default {
name: "SetterUpper",
};
</script>
Move your q-dialog to your parent component. That way you won't have to pass any props or emit events to make sure that multiple components know the state of your v-modal variable.
Like so
MainLayout.vue
<template>
<q-layout view="lHh Lpr lFf">
<q-dialog v-model="dialogEnabled" persistent>
<SetterUpper />
</q-dialog>
<q-btn
unelevated
icon="settings"
label="Settings"
color="primary"
v-on:click="dialogEnabled = true"
/>
</q-layout>
</template>
<script>
import SetterUpper from 'components/SetterUpper'
export default {
name: 'MainLayout',
components: {
SetterUpper
},
data() {
return {
dialogEnabled: false
}
}
}
</script>
SetterUpper.vue
<template>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="settings" color="primary" text-color="white"/>
<span class="q-ml-sm">Placeholder</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup/>
<q-btn flat label="Save" color="primary" v-close-popup/>
</q-card-actions>
</q-card>
</template>
<script>
export default {
name: 'SetterUpper'
}
</script>

Laravel + Quasar #click to show modal not working

I'm trying to use quasar for the first time and I am trying to show a modal with a button but somehow the value won't turn to true which triggers my modal to show.
Home.vue
<template>
<div>
<q-layout>
<q-list highlight class="bg-white">
<q-list-header>Details
<q-btn color="green-5" #click="openAdd">Add New</q-btn>
</q-list-header>
<!-- <q-search/> -->
<q-item>
<q-item-side>
<q-item-tile avatar>
<img src="https://cdn.quasar.dev/logo/svg/quasar-logo.svg">
</q-item-tile>
</q-item-side>
<q-item-main label="John Doe" />
<q-item-side right>
<div>
<q-btn flat round icon="edit" color="yellow-8" small/>
<q-btn flat round icon="delete" color="red-8" small/>
<q-btn flat round icon="visibility" color="green-6" small/>
</div>
</q-item-side>
</q-item>
</q-list>
</q-layout>
<Add></Add>
</div>
</template>
Home.vue(script)
<script>
let Add = require('./Add.vue');
export default {
name: 'app',
components: {Add},
data(){
return{
addActive : ''
}
},
methods: {
openAdd() {
this.addActive = '';
}
}
}
</script>
Add.vue(modal):
<template>
<div>
<q-modal v-model="addActive" ref="layoutModal" :content-css="{minWidth: '50vw', minHeight: '50vh'}">
<q-modal-layout>
<q-toolbar slot="header" color="green-7">
<div class="q-toolbar-title">
Header
</div>
</q-toolbar>
<q-toolbar slot="footer" color="green-7">
<div class="q-toolbar-title">
Footer
</div>
<q-btn color="green-10" label="Save">Save</q-btn>
<q-btn color="red-9" #click="open = false" label="Close">Cancel</q-btn>
</q-toolbar>
</q-modal-layout>
</q-modal>
</div>
</template>
<script>
import {
QToolbar,
QToolbarTitle,
QBtn,
QModal,
QModalLayout
} from 'quasar-framework'
export default {
name: 'app',
components: {
QToolbar,
QToolbarTitle,
QBtn,
QModal,
QModalLayout
},
data() {
return {
layoutStore: {
view: 'lHh Lpr lFf',
reveal: false,
leftScroll: true,
rightScroll: true,
leftBreakpoint: 996,
rightBreakpoint: 1200,
hideTabs: false
}
}
},
data () {
return {
addActive: false
}
}
}
</script>
<style lang="stylus">
</style>
Any help is appreciated! Cheers and thanks!!
I tried turning the Add.vue's open value to return true and the modal shows up. But when I try to use the button to turn its value to true it does not work somehow.
You're defining
addActive as empty string in Home.vue
Have you tried
addActive: false (or true if you want it to open right away)
but you need to define in props as Boolean and pass those accordingly and not in data()

Categories

Resources