Vue Vuetify open dialog component onclick - vue.js

Edit: I figured out why the dialog isnt opening. The child component is not receiving the openComment event. I checked in the root component, and that is receiving the event correctly. Any suggestions on why sibling components are not receiving the events? It could also be because I am not using the component in anything, but because it is the modal itself, I dont really want to import it to any other vue file.
I am trying to figure out a way to open a modal dialog from my toolbar. The toolbar lives in one component file, and the dialog lives in another component file. I am trying to acheive this using events, but i cant seem to get it to trigger. What i have tried is sending a custom even which is supposed to see the set the vmodel for the dialog to true. I am using Vuetify to create the dialogs.
My dialog component file is:
<template>
<v-dialog persistent
v-model="commentDialog"
transition="dialog-transition">
<v-card>
<v-card-title primary-title>
Add a comment
</v-card-title>
<v-card-text>
<v-flex xs12 sm6 md4>
<v-text-field label="Legal first name*" required></v-text-field>
</v-flex>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script>
import { bus } from '../main'
export default {
name: 'CommentModal',
data() {
return {
commentDialog: false
}
},
created() {
bus.$on('openComment', function () {
this.commentDialog = true
})
},
}
</script>
<style>
</style>
The toolbar component includes the following:
<template>
<v-btn fab small
#click="commentThis($event)"
<v-icon>fas fa-comment</v-icon>
</v-btn>
</template>
<script>
commentThis: function (e) {
bus.$emit('openComment')
}
</script>
Bonus and follow up question to this question would be how can i see the event bus on the vue chrome debugger?

The problem with function context
created() {
const self = this
bus.$on('openComment', function () {
self.commentDialog = true
})
},
or
bus.$on('openComment', () => (this.commentDialog = true))
Bonus and follow up question to this question would be how can i see the event bus on the vue chrome debugger?
import Vue from 'vue'
const bus = new Vue()
window.__myBus = bus
export { bus }

I solved the issue. It seems like i had to call the component somewhere in my toolbar component vue file. So i called it as ` and that enables the CommentModal component to react the the sent events. If the component is not called anywhere in the sibling components, then it does not react to any of the events.
But I would love to hear if there is a better solution to this. It feels a bit hacky to me.

Related

Components rendering before Vuex state updates

I'm having trouble with vuex getters; On the first log in, with vuex in its initial state, actions which set some state properties are dispatched, however, subsequent usage of getters still retrieve null (the initial value for state properties)
I have the following in my vue component's script:
beforeCreate() {
store.dispatch('getSomething', 1).then(() => {
this.loading = false
})
},
computed: {
...mapGetters({
something: 'getSomething'
})
}
in the template:
<v-row v-if="!loading">
...
<span class="text-16">{{ something.name }}</span>
...
</v-row>
In the Something store:
const getters = {
getSomething: state => new Something(
state.something.id,
state.something.name,
state.something.description,
)
}
My expectation is that the action would be called before the component is loaded and being synchronous, the state should be filled by the said action which commits a mutation that sets the state.
Instead I get the following error which points to the getter:
TypeError: Cannot read properties of null (reading 'id')
at getSomething(something.js?62ce:12:1)
Update (mwe)
<template>
<v-row v-if="!loading">
<v-col class="mt-3" cols="12" lg="3">
<base-card>
<v-row>
<v-col class="text-center" cols="12">
<v-avatar>
<v-icon>mdi-liquid-spot</v-icon>
</v-avatar>
<div class="card-title ma-1 text-h5">{{ something.name }}</div>
<div class="d-flex align-center justify-center">
<span class="text-16">{{ something.description}}</span>
</div>
</v-col>
</v-row>
</base-card>
</v-col>
</v-row>
</template>
<script>
import store from "#/store";
import {mapGetters} from "vuex";
export default {
name: "Dashboard",
beforeCreate() {
// getSomething action
store.dispatch('getSomething', 2).then(() => {
this.loading = false
})
},
computed: {
...mapGetters({
// getSomething getter
something: 'getSomething'
})
},
methods: {},
data() {
return {
loading: true
}
}
}
</script>
<style scoped>
</style>
Your problem has (most likely) nothing to do with vuex.
This is probably a case of replacing a component with another component of the same type. When that happens, unless the component is key-ed using a unique primitive identifier, the component instance is reused (by Vue), so beforeCreate is only called once.
After that, whenever the component is updated, the beforeCreate hook is no longer called, (only beforeUpdate and updated hooks are called).
You either key the child component using a unique primitive identifier (perhaps something.id!?). Or you use beforeUpdated hook.
Another important aspect is the store action is asynchronous. Do not expect the creation of the component or its mounting to be waiting for the action to resolve. If that's what you want, you should call the action from parent component and condition the rendering of the child component (using v-if) on something that gets set when the action has resolved (and which gets unset when you dispatch the action again, to get another "something").
If my answer doesn't help you, consider creating a runnable Minimal, Reproducible Example. What you posted so far is not enough to create one and test potential solutions.
This makes your question unanswerable, renders it useless for future users having a similar problem and will likely result in the question being closed as off-topic.

Vue manually mounting & remounting components

I have the following stripped down code that dynamically mounts components from a dropdown list:
<template>
<v-app>
<v-container>
<v-layout>
<v-select label="Providers"
single-line
:items="providers"
item-text="txt"
item-value="val"
:v-model="provider"
v-on:change="setProvider" />
<div ref='provider' id='provider' />
</v-layout>
</v-container>
</v-app>
</template>
<script>
import Provider1 from './components/Provider'
import Provider2 from './components/Provider2'
import Vue from 'vue'
import vuetify from './plugins/vuetify';
export default {
data: () => {
return {
provider: null,
providers: [
{txt: 'a', val: Provider1},
{txt: 'b', val: Provider2}
],
};
},
methods: {
setProvider(val) {
console.log(this.$refs.provider);
if (this.provider) {
// unmount and/or re-create #provider dom element
}
this.provider = new (Vue.extend(val))({
vuetify,
}).$mount('#provider');
}
},
}
</script>
First selection works great, subsequent selections graces my console window with "[Vue warn]: Cannot find element: #provider"
What should be placed in // unmount and/or re-create #provider dom element?
Also, if these need to be separately created questions, let me know:
What happens to the dom element? It doesn't get replaced as console.log(this.$refs.provider); clearly shows.
Why is manually mounting components advised against everywhere by everyone? Pending info on the unmount code, this way of doing it looks much more elegant than a slough of v-ifs would look in my opinion.
(edit: added 3rd question)
Are there any downsides to mixing vanilla markup with Vuetify's such as the above <div />?
Thanks
(edit: revised, working code. I've added an emit for extra fun)
<template>
<v-app>
<v-app-bar app />
<v-main>
<v-select label="Providers"
:items="providers"
v-model="provider" />
<component :is="provider" #fb="feedback" />
</v-main>
</v-app>
</template>
<script>
import Provider1 from './components/Provider'
import Provider2 from './components/Provider2'
export default {
data: () => {
return {
provider: null,
providers: [
{text: 'a', value: Provider1},
{text: 'b', value: Provider2}
],
};
},
methods: {
feedback(v) {
alert(v);
}
}
}
</script>
If your objective is to change between components on-the-fly, you can use the is Vue keyword to build dynamic components. That way you won't need to use v-ifs to control which component must render.
I'm also pretty sure you're not supposed to $mount inside components I believe that causes some side-effects and isn't generally good practice, since there are at least other ways to do it.
About mixing Vuetify and vanilla HTML, there's mostly no problem there. Some of Vuetify's selectors are pretty specific (like using scrollable in a v-dialog with v-card) but most are more general.

Access to a slot function inside a child component in VusJS

I'm trying to use tiptap. Actually it works, but what I'm trying to do is to access the "isActive" slot from outside the editor component, and I don't know how to do it.
Here is a codesandbox example: https://codesandbox.io/s/v07xnxo807?file=/src/App.vue
You see the Editor component is called from the App.vue. The buttons in the Editor component are activated depending on the "isActive" slot functions.
What I would like is to access this slot to get for example the value of isActive.bold() from the App.vue, in order to update the model of a "multiple button" you can find on Vuetify: https://vuetifyjs.com/fr-FR/components/button-groups/
Here is for example what I could have:
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
<v-btn-toggle
v-model="toggle_multiple"
dense
background-color="primary"
dark
multiple
class="my-2"
>
<v-btn :color="isActive.bold()?'red':'green'" #click="commands.bold">
<v-icon>mdi-format-bold</v-icon>
</v-btn>
<v-btn #click="commands.italic">
<v-icon>mdi-format-italic</v-icon>
</v-btn>
<v-btn #click="commands.strike">
<v-icon>mdi-format-strikethrough</v-icon>
</v-btn>
<v-btn #click="commands.underline">
<v-icon>mdi-format-underline</v-icon>
</v-btn>
<v-btn #click="commands.blockquote">
<v-icon>mdi-format-quote-open</v-icon>
</v-btn>
</v-btn-toggle>
</editor-menu-bar>
And the toggle_multiple would be computed depending on the different "isActive" function values.
I already tried:
computed: {
toggle_multiple: function () {
let t = []
if (editor) {console.log("Bold: "+editor.isActive.bold())}
return t
}
},
But I receive this error: error 'editor' is not defined
I'm open to any suggestion.
Thanks in advance.
Property isActive is stored in the tiptap instance (in your case it's this.editor):
In HTML:
<div>{{editor.isActive.bold()}}</div>
In JS:
<div>{{toggle_multiple}}</div>
computed: {
toggle_multiple () {
// Keep in mind, other properties like ".isActive.heading()" will be undefined
// until you import the extension for it.
// So the function "heading" below exists only if you're using that extension
// console.log(this.editor.isActive.heading({ level: 2 })
return this.editor.isActive.bold()
}
}

Vuetify v-dialog do not show in spite of value attribute equal to true

I am using vuex store state to show/hide Vuetify v-dialog in my NuxtJS app. Following are the code excerpt:
Vuex Store:
export const state = () => ({
dialogOpen: false
});
export const mutations = {
setDialogToOpen(state) {
state.dialogOpen = true;
},
setDialogToClosed(state) {
state.dialogOpen = false;
}
};
export const getters = {
isDialogOpen: state => {
return state.dialogOpen;
}
};
Dialog Component:
<v-dialog
v-model="isDialogOpen"
#input="setDialogToClosed"
max-width="600px"
class="pa-0 ma-0"
>
...
</v-dialog>
computed: {
...mapGetters("store", ["isDialogOpen"])
},
methods: {
...mapMutations({
setDialogToClosed: "store/setDialogToClosed"
})
}
This all works fine but when I redirect from one page to another page like below it stops working.
this.$router.push("/videos/" + id);
I hit browser refresh and it starts working again. Using the Chrome Vue dev tools, I can see the state is set correctly in the store as well as in the v-dialog value property as shown below
In Vuex store
In v-dialog component property
Yet the dialog is not visible. Any clue what is happening?
I am using NuxtJS 2.10.2 and #nuxtJS/Vuetify plugin 1.9.0
Issue was due to v-dialog not being wrapped inside v-app
My code was structured like this
default layout
<template>
<div>
<v-dialog
v-model="isDialogOpen"
#input="setDialogToClosed"
max-width="600px"
class="pa-0 ma-0"
>
<nuxt />
</div>
</template>
Below is the code for index page which replaces nuxt tag above at runtime.
<template>
<v-app>
<v-content>
...
</v-content>
</v-app>
</template>
So, in the final code v-dialog was not wrapped inside v-app. Moving v-app tag to default layout fixed it
<template>
<v-app>
<v-dialog
v-model="isDialogOpen"
#input="setDialogToClosed"
max-width="600px"
class="pa-0 ma-0"
>
<nuxt />
</v-app>
</template>

Why is the this.$on() callback triggered upon mount of a component?

I have a component which $emit to its parent upon some activity.
The parent listens to this event and triggers a function upon reception. This is assembled in mount():
<template>
<v-app>
<v-toolbar fixed app>
<v-toolbar-title v-text="title"></v-toolbar-title>
<v-spacer></v-spacer>
<v-btn color="error" dark large>Large Button</v-btn>
</v-toolbar>
<v-content>
<new-case v-on:dirty="updateDirty"></new-case>
</v-content>
<v-footer app>
<span>© 2017</span> dirty: {{dirty}}
</v-footer>
</v-app>
</template>
<script>
export default {
data() {
return {
case: {
id: null,
title: null,
},
cases: [],
title: 'Vuetify.js',
dirty: false,
}
},
watch: {
dirty: () => {
console.log('requesting relaod of data')
}
},
methods: {
updateDirty(what) {
console.log('requesting update of dirty state '+what)
this.dirty = what
}
},
mounted() {
this.$on('dirty', this.updateDirty())
}
}
</script>
The whole mechanism works fine (the emmited even is correctly handled by the parent), except that when the component is mounted, I see in the console
17:36:01.380 App.vue?ea99:36 requesting update of dirty state undefined
17:36:01.449 App.vue?ea99:31 requesting relaod of data
17:36:01.561 backend.js:1 vue-devtools Detected Vue v2.5.13
Why is this.updateDirty() triggered upon the mount of the component? (even though nothing was emitted yet - not only the component which would emit something is not used yet, but the DevTools Vue panel does not show any events yet)
The issue is with you $on call itself. The parenthesis after updateDirty, this.$on('dirty', this.updateDirty()), is the culprit, it is telling JS to run the function and store the result as the event handler. Try this.$on('dirty', this.updateDirty) instead so you're passing the reference to the function not the result.