Opening a modal component with a button in another ccomponent using Vuetify - vue.js

I have 2 components. Modal and Navbar. I'm trying to open Modal using a button in Navbar using vuex. I have a state called modalIsOpen. This states value changes from false to true when clicked on the button in Navbar but only a blank row is rendered as a modal and modal content is not shown. I could not figure out what is wrong.
At the beginning i thought it was a vuetify v-dialog problem. But ive tried other libraries too. And as i said nothing worked yet.
Here is the components ,app.vue and store.js.
AddUpdateModal.vue:
<template>
<v-dialog>
<v-card width="50%" height="50%">
<v-card-title class="text-h5 grey lighten-2">
Privacy Policy
</v-card-title>
<v-card-text>
Lorem
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" text #click="dialog = false">
I accept
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
name: "AddUpdateModal",
components: {},
data: () => ({}),
}
</script>
<style>
.v-card {
display: block !important;
}
.v-easy-dialog {
height: 500px !important;
}
</style>
NavBar.vue:
<template>
<div id="Navbar">
<v-row>
<v-btn color="primary" #click.stop="openModal" rounded>
Add / Update
</v-btn>
<v-btn color="primary" #click="closeModal" rounded>
Refresh
</v-btn>
</v-row>
<v-row>
<v-divider class="grey darken-4"></v-divider>
</v-row>
</div>
</template>
<script>
export default {
name: 'NavBar',
components: {},
data: () => ({}),
methods: {
openModal() {
this.$store.commit('openModal');
},
closeModal() {
this.$store.commit('closeModal')
}
},
}
</script>
App.vue:
<template>
<v-app>
<v-container>
<NavBar />
<br>
<div class="parent">
<!-- <AddButton></AddButton> -->
<v-btn #click="konsol">Konsol</v-btn>
<div id="modal" v-if="$store.state.modalIsOpen">
<template>
<AddUpdateModal></AddUpdateModal>
</template>
</div>
<v-row>
<v-col>
<DataTable />
</v-col>
<v-divider class="grey darken-4" vertical inset></v-divider>
<v-col>
<PieChart />
</v-col>
</v-row>
</div>
</v-container>
</v-app>
</template>
<script>
import NavBar from './components/NavBar.vue';
import PieChart from './components/PieChart.vue';
import DataTable from './components/DataTable.vue';
// import AddButton from './components/AddButton';
import AddUpdateModal from './components/AddUpdateModal';
// eslint-disable-next-line no-unused-vars
import store from './store/store'
// import axios from 'axios';
export default {
name: 'App',
components: {
NavBar,
AddUpdateModal,
PieChart,
DataTable,
// AddButton,
},
created() {
this.$store.dispatch("initApp")
},
data: () => ({
}),
methods: {
konsol() {
console.log("modalIsOpen", this.$store.state.modalIsOpen)
}
},
};
</script>
<style>
* {
margin: 5px;
}
.v-dialog__container {
display: unset !important;
position: relative !important;
}
</style>
store.js:
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
binance24HrData: [],
modalIsOpen: false,
},
mutations: {
initPortfolioDetails(state, newCoin) {
state.binance24HrData = newCoin;
},
openModal(state) {
state.modalIsOpen = true;
},
closeModal(state) {
state.modalIsOpen = false;
},
},
actions: {
initApp(context) {
axios
.get("https://api2.binance.com/api/v3/ticker/24hr")
.then((response) => {
context.commit("initPortfolioDetails", response.data);
console.log("Binance", response.data);
});
},
openModal({ commit }) {
commit("openModal");
},
closeModal({ commit }) {
commit("closeModal");
},
},
getters: {
getCoinsDetails(state) {
return state.binance24HrData;
},
},
});
export default store;
main.js:
import Vue from "vue";
import App from "./App.vue";
import vuetify from "./plugins/vuetify";
import store from "./store/store.js";
Vue.config.productionTip = false;
new Vue({
vuetify,
store,
render: (h) => h(App),
}).$mount("#app");

I figured it out: all I had to do was add v-model to v-dialog. I thought it was unnecessary because I already had a v-if that wrapped the component containing the v-dialog. I assumed that with this requirement fulfilled, it should render the child component, but it didn't because I didn't have v-model in v-dialog.

Related

VueJS Supense only works whenI land on the Async component

I read this part of documentation: https://vuejs.org/guide/built-ins/suspense.html#combining-with-other-components
I have in my menu 2 links: "Home" and "Tools". If I land on my root page, that is to say the "Home" route, then I go to the "Tools" route, the "Loading..." message doesn't show. I have to wait 2 seconds, though, meaning the async component waits to be loaded to be displayed.
If I go to the "Tools" route directly, I see the message. I don't understand what I did wrong because I want the "Loading..." message to show when I load this async component, even if I come from another route.
In my App.vue I have this:
<script setup>
import Connexion from '#/components/Connexion.vue'
import { ref } from 'vue'
const drawer = ref(null);
</script>
<template>
<v-app>
<v-navigation-drawer
expand-on-hover
rail>
<Suspense>
<Connexion />
<template #fallback>
Chargement...
</template>
</Suspense>
<v-divider></v-divider>
<v-list density="compact" nav>
<v-list-item prepend-icon="mdi-home" title="Home" value="home" #click="$router.push('/')"></v-list-item>
<v-list-item prepend-icon="mdi-account-multiple" title="Tools" value="tools" #click="$router.push('/tools')"></v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar>
<v-toolbar-title>My app</v-toolbar-title>
</v-app-bar>
<v-main fluid fill-width>
XXX
<v-container
fluid
>
<router-view v-slot="{ Component }">
<template v-if="Component">
<Transition mode="out-in">
<keep-alive>
<Suspense>
<component :is="Component" />
<template #fallback>
Loading...
</template>
</Suspense>
</keep-alive>
</Transition>
</template>
</router-view>
</v-container>
</v-main>
</v-app>
</template>
<style>
/* we will explain what these classes do next! */
.v-enter-active,
.v-leave-active {
transition: opacity 0.2s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
And here is the Tools.vue component:
<script>
import { defineComponent } from "vue";
export default defineComponent({
async setup() {
const apiUrl = import.meta.env.VITE_APP_API_URL;
console.log("loading...")
const demandes = await fetch(`${apiUrl}/tools`)
.then((r) => r.json())
.catch((e) => {
console.log(e);
return [];
});
await new Promise(r => setTimeout(r, 2000));
console.log("LOADED!")
return {
demandes,
}
}
})
</script>
<template>
<div>
<h1>Tools</h1>
<ul>
<li
v-for="tool in tools"
:key="`tool-${tool.id}`"
>
{{ tool }}
</li>
</ul>
</div>
</template>
If it has any interest, here is my router file:
import { createRouter, createWebHistory } from 'vue-router';
import { nextTick } from 'vue';
const routes = [
{
path: '/',
name: 'Home',
component: () => import('#/views/Home.vue'),
meta: {
title: 'Home',
},
},
{
path: '/tools',
name: 'Tools',
component: () => import('#/views/Tools.vue'),
meta: {
title: 'Tools',
},
},
];
const router = createRouter({
base: import.meta.env.VITE_APP_PUBLICPATH,
history: createWebHistory(import.meta.env.VITE_APP_BASE_URL),
routes,
});
const DEFAULT_TITLE = import.meta.env.VITE_APP_TITLE;
router.afterEach((to) => {
nextTick(() => {
document.title = to.meta.title ? `${DEFAULT_TITLE} - ${to.meta.title}` : DEFAULT_TITLE;
});
});
export default router;
And the Home.vue:
<script setup>
</script>
<template>
<h1>Home</h1>
</template>
<style scoped>
</style>

Vue vuetify Dialog How to tigger dialog component without click

How can I tigger the dialog from main file? I have no idea how to tigg ConfirmationDialog value "dislog" to true. Or any other method to do it? Dont want to group the code as a one file.
That is a component called ConfirmationDialog.vue
<template>
<v-layout row justify-center>
<v-btn color="primary" dark #click.native.stop="dialog = true">Open Dialog</v-btn>
<v-dialog v-model="dialog" max-width="290">
<v-card>
<v-card-title class="headline">Use Google's location service?</v-card-title>
<v-card-text>Let Google help apps determine location. This means sending anonymous location data to Google, even when no apps are running.</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="green darken-1"
flat="flat"
#click.native="dialog = false"
>Disagree</v-btn>
<v-btn color="green darken-1" flat="flat" #click.native="dialog = false">Agree</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-layout>
</template>
<script>
export default {
data() {
return {
dialog: false
}
}
}
</script>
Main File:
<template>
<div class="row">
<div class="col-12">
<confirmationDialog></confirmationDialog>
</div>
</div>
</template>
<script>
import confirmationDialog from '../confirmationDialog'
export default {
data() {
return {}
},
components: {
confirmationDialog
},
methods: {
update() {
// Todo: Tigger Confirmation Dislog
}
},
}
</script>
Add a prop called show to the child component (confirmationDialog ) and bind to a parent property :
confirmationDialog
...
<script>
export default {
props:['show'],
data() {
return {
dialog: false
}
},
mounted(){
this.dialog=this.show;
}
}
</script>
Main
<template>
<div class="row">
<div class="col-12">
<confirmationDialog :show="showDialog"></confirmationDialog>
</div>
</div>
</template>
<script>
import confirmationDialog from '../confirmationDialog'
export default {
data() {
return {
showDialog:false,
}
},
components: {
confirmationDialog
},
methods: {
update() {
this.showDialog=true;
}
},
}
</script>

Vue Passing Data from component to another component

I am making a "create account" flow for user and I am not able to pass data from one component to another. The first component has radio buttons with options of "tenant", "landlord", "contractor". Once the user selects "tenant", then the data should pass to the next step where they fill out a form with name and all that good stuff.. once they submit, it should all go together to the back end.
acc-for.vue with radio buttons component below.
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
Are you a?
</v-card-title>
<v-radio-group v-model="selectedValue" #change="selectedAcc">
<v-radio
v-for="account in accountType"
:key="account.name"
:value="account.name"
:label="account.name"
></v-radio>
</v-radio-group>
<v-row>
<v-btn rounded color="black" class="white--text" href="/login"
>Back</v-btn
>
<v-btn
rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
<!-- href="/create-acc" -->
</v-row>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
data() {
return {
selectedValue: false,
};
},
computed: {
accountType() {
return this.$store.state.accountType;
},
selected() {
return this.$store.state.selectedAccType;
},
},
methods: {
...mapMutations(["SELECTED_ACCOUNT_TYPE"]),
selectedAcc(e) {
this.$emit("selected-accountType", e);
},
},
};
</script>
<style></style>
createAccount.vue this component has the form for the fName and lName and all that good stuff..
<template>
<div>
<v-app
style="background-image: url('https://blog.modsy.com/wp-content/uploads/2019/06/D2_Full.jpg')"
>
<v-container class="pa-12">
<v-row>
<v-card class="pa-16">
<v-card-title>
{{ selectedTypeUser }}
</v-card-title>
<v-form>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_fName"
label="First Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_lName"
label="Last Name"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_email"
label="Email"
/>
<v-text-field
class="pa-4"
type="text"
v-model="newUser_password"
label="Password"
/>
<v-row>
<v-btn rounded color="black" class="white--text" href="/acc-for"
>Back</v-btn
>
<v-btn
#click="registerUser"
rounded
color="black"
class="white--text"
>Next</v-btn
>
</v-row>
</v-form>
</v-card>
</v-row>
</v-container>
</v-app>
</div>
</template>
<script>
import { mapMutations } from "vuex";
import axios from "axios";
export default {
data() {
return {
newUser_fName: "",
newUser_lName: "",
newUser_email: "",
newUser_password: "",
};
},
methods: {
...mapMutations(["ADD_USER"]),
registerUser() {
let config = {
headers: {
"Content-Type": "application/json",
},
};
axios
.post(
"http://localhost:7876/createUser",
{
fName: this.newUser_fName,
lName: this.newUser_lName,
email: this.newUser_email,
password: this.newUser_email,
},
config
)
.then((response) => {
console.log(response.statusText);
})
.catch((e) => {
console.log("Error: ", e.response.data);
});
},
},
};
</script>
store.js (state) is below
// import vue from "vue";
import vuex from "vuex";
import axios from "axios";
import Vue from "vue";
Vue.use(vuex, axios);
export default new vuex.Store({
state: {
users: [],
accountType: [
{ name: "Tenant" },
{ name: "Landlord" },
{ name: "Contractor" }
],
selectedAccType: [],
},
mutations: {
ADD_USER: (state, payload) => {
state.users.push(payload.user);
},
SELECTED_ACCOUNT_TYPE: (state, payload) => {
state.selectedAccType.push(payload)
}
},
actions: {
addUser: ({ commit }, payload) => {
commit("ADD_USER", payload);
},
selectAcc: ({ commit }, payload) => {
commit("SELECTED_ACCOUNT_TYPE", payload)
}
},
// getters: {
// addAccountType(state, e) {
// state.accountType.push(e)
// },
// },
});
I see this on the button:
<v-btn rounded
color="black"
class="white--text"
#click="selected(accountSelected)"
>Next</v-btn
>
But I'm not finding a method with that name in your attached code. Double check that your names are all matched up and that you are calling the correct function on the click event.

VueJS/Vuetify: Change colwidth with animation/transition

this is my app component:
<template>
<v-app id="inspire">
<v-main>
<v-container fluid>
<v-row>
<v-col :cols="dynamicCol">
<worldmap></worldmap>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>
<script>
import WorldMap from "./components/WorldMap";
export default {
props: {
source: String
},
components: {
worldmap: WorldMap
},
computed: {
changeDynamicCol() {
return this.$store.state.lineData.data.length;
}
},
watch: {
changeDynamicCol(value) {
if (value > 0) {
this.dynamicCol = 9;
}
}
},
data: () => ({
dynamicCol: 12,
drawer: null
}),
created() {
this.$vuetify.theme.dark = true;
}
};
</script>
<style scoped></style>
My App starts with an empty array and when I receive data, the first container small shrink from a width of 12 to 9. The code itself works fine, however the chart inside reacts to changes in the viewport, so get´s currently just cut off. In general it looks much better when this is animated.
Does anyone know if and how I can change the width of v-col with a transition or other animation?
I think you use watchers in wrong way.
Here I show how I see your case, but I don't sure if it works fine.
If you have more question, I can answer in comments
<template>
<v-app id="inspire">
<v-main>
<v-container fluid>
<v-row>
<v-col :cols="columnCount">
<worldmap></worldmap>
</v-col>
</v-row>
</v-container>
</v-main>
</v-app>
</template>
<script>
import WorldMap from "./components/WorldMap";
import { mapState } from 'vuex'
export default {
props: {
source: String
},
components: {
worldmap: WorldMap
},
computed: {
...mapState({
lineData: state => state.lineData.data.length
}),
columnCount() {
if (this.lineData > 0) {
return 9
}
return 12
}
},
data: () => ({
drawer: null
}),
};
</script>

vue-router not rendering component, but changing url on this.$router.push

I'm trying to redirect to a Menu after pressing a button, I have followed this tutorial
but it's not working,
When Pressing my button the url updates, but stays in the same view, it's also adding /#/ to my url instead of following what I coded in routes.js
I'm getting the next error on console
Uncaught (in promise)
NavigationDuplicated {
_name: "NavigationDuplicated",
name: "NavigationDuplicated",
message: "Navigating to current location ("/menu") is not allowed", stack:
When pressing the button the url turns into http://localhost:8080/#/menu instead of
http://localhost:8080/menu
If I manually type the url http://localhost:8080/menu turns into this
http://localhost:8080/menu/#/
Please help, I'm fairly new to vuejs
This is the structure of my project
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import vuetify from './plugins/vuetify'
import routes from './routes'
import 'roboto-fontface/css/roboto/roboto-fontface.css'
import '#mdi/font/css/materialdesignicons.css'
Vue.config.productionTip = false
Vue.use(VueRouter)
const router = new VueRouter({routes});
new Vue({
render: h => h(App),
router,
vuetify
}).$mount('#app')
App.vue
<template>
<div id="app">
<Home/>
</div>
</template>
<script>
import Home from './views/Home.vue'
import 'material-design-icons-iconfont/dist/material-design-icons.css';
export default {
name: 'App',
components: {
Home
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
my routes.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home.vue'
import About from './views/About.vue'
import TraceabilityMenu from './views/TraceabilityMenu.vue'
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Home, name: 'home' },
{ path: '/menu', component: TraceabilityMenu, name: 'traceability-menu' },
{path: '/about', component: About, name: 'about'}
]
export default routes;
My Home.vue which is the first view to load(by the App.vue)
<template>
<v-app id="inspire">
<v-app-bar app color="indigo" dark>
<v-toolbar-title>Project Traceability</v-toolbar-title>
<template>
<v-spacer />
<v-btn color="primary" #click="showPopupLogin()" :to="{ name: 'login'}" >Ingresar</v-btn>
</template>
</v-app-bar>
<PopupLogin v-show="showLogin"/>
<v-content>
<v-container
class="fill-height"
fluid
>
<v-row
align="center"
justify="center"
>
<v-col class="text-center">
</v-col>
</v-row>
</v-container>
</v-content>
<v-footer
color="indigo"
app
>
</v-footer>
</v-app>
</template>
<script>
import PopupLogin from '#/components/PopupLogin.vue';
export default {
props: {
source: String,
},
data: () => ({
showLogin : false
}),
components: {
PopupLogin,
},
methods: {
showPopupLogin() {
this.showLogin = !this.showLogin
}
}
}
</script>
The component PopupLogin
<template>
<v-app id="inspire">
<v-content>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col cols="12" sm="8" md="4">
<v-card class="elevation-12">
<v-toolbar color="primary" dark flat >
<v-toolbar-title>Iniciar sesión</v-toolbar-title>
<v-spacer />
<v-tooltip bottom>
</v-tooltip>
</v-toolbar>
<v-card-text>
<!-- Formulario de login-->
<v-form v-model="validForm" ref="formLogin">
<v-text-field
required
label="Usuario"
:rules="nameRules"
name="login"
type="text"
v-model="existingUser.username"/>
<v-text-field
required
id="password"
prepend-icon="lock"
label="Contraseña"
name="password"
type="password"
v-model="existingUser.password"/>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn color="primary" #click="loginUser()">Ingresar</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'PopupLogin',
props: {
source: String
},
data: () => ({
validForm : false,
//objetos
existingUser : {}
}),
methods: {
//Funcion que llamara al servicio de login en backend
loginUser() {
this.$router.push({path: '/menu'});
}
}
}
</script>
TraceabilityMenu.vue the view which I'm trying to render after the press of the button Login
<template>
<v-app id="inspire">
<div>RENDER ME!</div>
</v-app>
</template>
<script>
export default {
props: {
source: String,
},
data: () => ({
drawer: null,
}),
}
</script>
On your main.js file try changing
const router = new VueRouter({routes});
to
const router = new VueRouter({routes, mode: 'history'});
Edit: Also check if you have included the router-view tag on your root component App.vue.