Vue + Vuetify reuse snackbar / alert - vue.js

I'm new to Vue I wanted to reuse the snackbar / alert box from vuetify to each of my components. I did it by copy pasting the code for each component which makes it very messy and hard to maintain.
How do I reuse this for each of my view vue component?
Please see my example code below.
Vue component < template >
<v-snackbar
v-model="snackbar.appear"
:color="snackbar.color"
:timeout="snackbar.timeout"
:left="snackbar.x === 'left'"
:right="snackbar.x === 'right'"
:top="snackbar.y === 'top'"
>
<v-row>
<v-icon class="mx-2" size="18" dark>{{ snackbar.icon }}</v-icon>
{{ snackbar.text }}
</v-row>
<v-btn dark text #click="snackbar.appear = false">OKAY</v-btn>
</v-snackbar>
Vue component < script >
snackbar: {
appear: false,
icon: '',
text: '',
color: 'success',
timeout: 2500,
x: 'right',
y: 'top',
},
axios
.post('/api/department-objective', { corporate_objective_id, objective, description })
.then(response => {
this.snackbar.appear = true
this.snackbar.text = response.data.text
this.snackbar.icon = response.data.icon
})
.catch(error => {
console.log(error)
this.alert = true
this.allerror = error.response.data.errors
})

I often add application wide alert messages to the root application's component, like e.g. an App component building up the base layout and bind it's visibility to the presence of an error or notification property in a central vuex store.
See this answer for details

You can create a snackbar component , which you can import in App.vue so that it's available to the every component , you can then trigger this component using vuex store as per you're requirement.
Here's an article which I used while learning to implement the same.
How to create a global snackbar using Nuxt, Vuetify and Vuex..
If you dont want to use nuxt , just refer to the article and you'll get the idea.

you can create a SnackBarComponent and import it on every other Component
App.vue
<template>
<SnackBarComponenent :stuffProp="yourProps" />
</template>
<script>
import SnackBarComponenent from './components/SnackBarComponent'
export default {
components:{
ProfileOverview
}
}
</script>

Related

Can't use a custom element even if it is registered

I have to use a custom element in my project, and it used to work perfectly.
I am now changing the design of the project so I'm restarting it from zero, and now I can't use a custom component.
I haven't changed anything in the custom element itself.
Here is the code for hte page where i want to use the element:
<template >
<v-app>
<body>
<v-img id="background" :src="images.background"></v-img>
<v-img id="logo-transparent" :src="images.logoTransparent"></v-img>
<DraggableDiv class="dragdiv">
<template>
</template>
</DraggableDiv>
</body>
</v-app>
</template>
And here is the script part:
import DraggableDiv from '../components/DraggableDiv.vue'
export default {
name: 'CreationCouches',
data: () => ({
components: {
DraggableDiv,
Palette
}
})
Can you help me ?
In fact I was adding the component into the data() but it has to be outside of it:
import DraggableDiv from '../components/DraggableDiv.vue'
export default {
name: 'CreationCouches',
data: () => ({
//some data
}),
components: {
DraggableDiv,
Palette
}
}

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.

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>

Passing multiple properties in vuejs

I'm quite new with VueJS. I'm working on a new project with VueCLI3 & VuetifyJS.
I'm trying to create a reusable components based on VuetifyJS components and would like to make things easier by passing multiple props in a separate file to render them at once in my new component files.
I found this article that explains a technique to achieve such thing.
https://alligator.io/vuejs/passing-multiple-properties/
Every time I need to render them I must import my seperate file.
component1.vue
<template>
<v-btn v-bind='buttonProps'>
Button 1
</v-btn>
</template>
<script>
import { buttonProps } from './props.js';
export default {
data: () => ({ buttonProps })
}
</script>
component2.vue
<template>
<v-btn v-bind='buttonProps'>
Button 2
</v-btn>
</template>
<script>
import { buttonProps } from './props.js';
export default {
data: () => ({ buttonProps })
}
</script>
Is there any way to register the file globally so it allows me to use it anywhere in the app like this?
props.js
export const buttonProps = {
color: 'primary',
small: true,
outline: true,
block: true,
ripple: true,
href: 'https://alligator.io'
}
main.js
import Props from 'props.js';
component3.vue
<template>
<v-btn v-bind='buttonProps'>
Button 3
</v-btn>
</template>
<script>
... // no import needed
</script>
You can use a mixin and register that mixin globally.
buttonPropsMixin
export default {
data() {
return {
buttonProps: {
color: 'primary',
small: true,
  outline: true,
block: true,
ripple: true,
href: 'https://alligator.io'
}
}
}
}
main.js
import buttonPropsMixin from '...';
Vue.mixin(buttonPropsMixin)
Note That each vue component has its own buttonProps, so if you change in one component the color it will not affect the other components!
If you want buttonProps to have the same state across all your components you can go the vuex way as Igor mentioned and use it with an mixin where you define the mapGetters in that mixin.
If data in props.js doesn't need to be reactive and all the components are children of some root component you could do this:
main.js:
import buttonProps from 'props.js';
new Vue({
el: rootComponentElement,
buttonProps: buttonProps,
render: h => h(rootComponent)
})
component.vue:
<template>
<v-btn v-bind='$root.$options.buttonProps'>
Button 3
</v-btn>
</template>
<script>
... // no import needed
</script>
Otherwise I would advice you to use Vuex or use the global bus method described here.

How to pass props to a vue component at initialization inside single file vue components (dependency injection in vue-loader)?

I'm building a TabbedDetailView reusable component in vue. The idea is that the tab-detail component receives a list of objects which have a title and a component. It then does the logic so that when you click on a tab, then the component is displayed. The problem is that this components have a prop that is a user_id. How do I insert this prop into the components from outside of the template (directly in the script)?
For example (using single file vue components with webpack):
TabDetail.vue
<template>
<div>
<nav class="tabs-nav">
<ul class="tabs-list">
<li class="tabs-item" v-for='tab in tabs'>
<a v-bind:class="{active: tab.isActive, disabled: !tab.enabled}" #click="switchTab(tab)">{{tab.title}}</a>
</li>
</ul>
</nav>
<div v-for='tab in tabs'>
<component :is="tab.detail" v-if='tab.isActive'></component>
</div>
</div>
</template>
<script>
export default {
name: 'NavigationTabs',
props: ['tabs'],
created: function() {
this.clearActive();
this.$set(this.tabs[0], 'isActive', true);
},
methods: {
clearActive: function() {
for (let tab of this.tabs) {
this.$set(tab, 'isActive', false);
}
}, switchTab: function(tab) {
if (tab.enabled) {
this.clearActive();
tab.isActive = true;
}
},
},
};
</script>
The idea is that this can be reused by only passing a props object with titles and components. eg. tabs = [{title: 'Example1', component: Component1}{title: 'Example2', component: Component2}] I want to be able to instantiate this components with props before passing them. eg. tabs = [{title: 'Example1', component: Component1({user_id: 5})}{title: 'Example2({user_id: 10})', component: Component2}]).
SomeComponent.vue
import Vue from 'vue';
import TabDetail from '#/components/TabDetail'
import Component1 from '#/components/Component1';
const Componenet1Constructor = Vue.extend(Component1);
export default {
data() {
return {
tabs: [
{title: 'Componenent 1', detail: new Component1Constructor({propsData: {user_id: this.user_id}})}
{title: 'Component 2', detail: Component2},
{title: 'Component 3', detail: Component3},
],
};
}, props: ['user_id'],
components: {'tab-detail': TabbedDetail},
}
<template>
<div>
<tab-detail :tabs='tabs'></tab-detail>
</div>
</template>
Component1.vue
export default {
props: ['user_id'],
};
<template>
<div>
{{ user_id }}
</div>
</template>
The approach above raises de error:
[Vue warn]: Failed to mount component: template or render function not defined.
I think this is a good idea because I'm trying to follow the dependency injection design pattern with components. Is there a better approach to this problem without using global state?
This is could be done via Inject Loader when using vue loader with single file vue components but it adds a lot of unnecessary complexity and it's mostly meant for testing. It seems like the preferred way of managing state is by using a global state management store like Vuex.