v-navigation-drawer controlled state from a sub-component - vuetify.js

I have a v-navigation-drawer which can be opened by clicking a button in a sub component.
So I changed v-model="drawer" to simply value="drawer" otherwise I get a warning about mutating a prop which makes sense (feels like doing some dirty angular double-way data binding ^^).
Here's the code:
layouts/default.vue:
<template>
<Header :toggleLeftMenu="toggleLeftMenu" />
<LeftMenu :show="showLeftMenu" :toggleLeftMenu="toggleLeftMenu" />
</template>
<script>
export default {
data() {
return {
showLeftMenu: true,
}
},
methods: {
toggleLeftMenu() {
this.showLeftMenu = !this.showLeftMenu;
},
}
}
</script>
components/layout/LeftMenu.vue:
<v-navigation-drawer
:value="show"
width="300"
clipped
fixed
app
>
This issue is that the drawer can be closed by clicking on the backdrop (on small devices). I need to plug the backdrop click to toggleLeftMenu prop, but according to the doc, this doesn't seem to be possible.
How can I achieve full control on the component? Is this #backdropClick event missing or something?
I tried to use #input but it creates an infinite loop which also makes sense.
Thanks
Using vuetify 2.6.1.

I changed v-model="drawer" to simply value="drawer" otherwise I get a warning about mutating a prop
This is not quite the right decision. Of course you should not use drawer as model, but you can create an internalDrawer prop in LeftMenu component, and leave the v-model where it is.
One of the possible ways to resolve your issue is to emit events from both sub-components into its parent.
So let's rewrite your LeftMenu component this way:
<template>
<v-navigation-drawer v-model="internalShow" width="200" clipped fixed app>
some drawer data
</v-navigation-drawer>
</template>
<script>
export default {
props: {
show: Boolean,
},
data() {
return {
internalShow: this.show,
};
},
watch: {
show (val) {
this.internalShow = val;
},
internalShow (val) {
if (val !== this.show) {
this.$emit("change-drawer-state");
}
},
},
};
</script>
In this case, every time when the internalShow state changes, an change-drawer-state event will be emitted.
Your Header component can be rewrited the same way:
<template>
<v-btn #click="$emit('change-drawer-state')">Drawer button</v-btn>
</template>
And this is the code of your parent component:
<template>
<div>
<Header #change-drawer-state="toggleLeftMenu" />
<LeftMenu :show="showLeftMenu" #change-drawer-state="toggleLeftMenu" />
</div>
</template>
<script>
import LeftMenu from "./LeftMenu";
import Header from "./Header";
export default {
components: {
LeftMenu,
Header,
},
data() {
return {
showLeftMenu: false,
};
},
methods: {
toggleLeftMenu() {
this.showLeftMenu = !this.showLeftMenu;
},
},
};
</script>
Both change-drawer-state event handlers are calling the same method - toggleLeftMenu and then the method changes show prop of navigation-drawer.
You can test this solution in a CodeSandbox playground.

Related

Vuejs custom Modal event bus is not firing

I have created my own custom Modal plugin in vuejs to be added to my Laravel 8 app. The problem I am facing is opening the modal.
I have created the plugin in my app.js file
const Modal = {
install (Vue) {
this.event = new Vue()
Vue.prototype.$modal = {
show (modal, params = {}) {
Modal.event.$emit('show', modal, params)
},
$event: this.event
}
}
}
Vue.use(Modal)
I have created two vue components for my modal
<!-- AppModal //-->
<template>
<transition name="modal">
<div v-if="visible">
<div class="app-modal" #click.prevent="$modal.hide(name)"></div>
<div class="app-modal-inner">
close
<slot name="body" :params="params"/>
</div>
</div>
</transition>
</template>
<script>
export default {
name: "AppModal",
data () {
return {
params: {},
visible: false,
}
},
props: {
name: {
required: true,
type: String,
}
},
methods: {
setVisible () {
this.visible = true
},
setHidden () {
this.visible = false
}
},
beforeMount() {
this.$modal.$event.$on('show', (modal, params) => {
if (this.name !== modal) {
return
}
this.params = params
this.setVisible()
})
},
}
</script>
<!-- AppNonMemberRegisterModal //-->
<template>
<app-modal name="register">
<template slot="header">
<h1 class="text-lg-left text-4xl border border-b-2">Register Now</h1>
</template>
<template slot="body" slot-scope="{ params }">
<p>You need to register in order to share, comment and like on the site</p>
</template>
</app-modal>
</template>
<script>
import AppModal from "../AppModal";
export default {
name: "AppNonMemberRegisterModal",
components: { AppModal },
}
</script>
Where I am firing the event I import the AppNonMemberRegisterModal and I have the following click event: #click.prevent="$modal.show('register')".
When the following code is reached Modal.event.$emit('show', modal, params) I get the following error messages in my cnosole console.log(Modal)
Vue 3 removes the Event API (i.e., $on, $off, etc.). The migration guide recommends using tiny-emitter to create your own event bus. That example shows how to create a global bus, but it seems your plugin just needs a local bus, which you could create like this:
// eventBus.js
import Emitter from 'tiny-emitter'
export function createEventBus() {
const emitter = new Emitter()
return {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}
}
Then in your plugin, create a global with app.config.globalProperties, referring to the locally created event bus:
// myPlugin.js
import { createEventBus } from './eventBus'
export default {
install(app) {
const eventBus = createEventBus()
app.config.globalProperties.$modal = {
show (modal, params = {}) {
eventBus.$emit('show', modal, params)
},
$event: eventBus
}
}
}
And install it:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import myPlugin from './myPlugin'
createApp(App).use(myPlugin).mount('#app')
Also be aware that your slot usage in AppRegisterModal.vue needs to be updated to the latest syntax (v-slot or # shorthand):
<app-modal name="register">
<!--
<template slot="header">
-->
<template #header>
<!--
<template slot="body" slot-scope="{ params }">
-->
<template #body="{ params }">
</app-modal>
demo

Laravel Nova | Arrow functions | computed property not working in Tool.vue

I have a computed property that is not working correctly. When reading the docs this is how it should be done for Vue 2.x. The code i have:
<template>
<div>
<button :disabled="isDisabled">Import configurator data</button>
<input class="input" type="file" id="file" v-on:change="setFile">
</div>
</template>
<script lang="js">
export default {
data: () => {
return {
importDisabled: true,
}
},
computed: {
isDisabled() {
return this.importDisabled;
},
},
methods: {
setFile: (e) => {
this.importDisabled = false;
},
}
}
</script>
Expected behaviour: Button enables when a file is selected.
Actual behaviour: Button stays disabled.
What am i missing here? A console.log within the isDisabled() methods shows it is only called once. It is not called after importDisabled changes.
Other info:
vue 2.6.12
laravel nova
Also note: Vue tools does not detect a Vue component in the inspector. But the Vue behaviour is working when loading the tool.
It seems arrow functions are bound to the parent context. That was the reason my code did not work. this is then not bound to the Vue instance but the parent (Window) which causes it not to work.
Wrong:
setFile: (e) => {
this.importDisabled = false;
},
Right:
setFile: function() {
this.importDisabled = false;
},
More information:
https://codingexplained.com/coding/front-end/vue-js/using-es6-arrow-functions-vue-js

owl.carousel vue.js component not rendering in lavarel application

I've been hunting and trying for the last handful of days but with no success.
I am trying to render a list of quotes to be displayed as a carousal on a login page.
I need to pull a list of quotes from a database which i have done. I then need to loop through the quotes and display them in the owl.carousel.
If i manually add the div.elements, it renders without a problem. when i add the element in a v-for loop, it does not display. Can someone please advise or guide me into the correct direction?
<template>
<carousel class="crsl" :autoplay="true" :nav="false" :items="1">
<div v-for="(item, index) in quotes" :key="item.id" v-text="item.quote"></div>
</carousel>
import carousel from 'vue-owl-carousel';
export default {
components: { carousel },
mounted() {
console.log('Component mounted.')
axios.post('api/quotes', {})
.then(response => {
this.quotes = response.data;
});
},
data: function () {
return {
quotes: null,
}
},
}
found a solution here
https://github.com/93gaurav93/v-owl-carousel/issues/16
My final code is as follows
<template>
<div v-if="quotes.length > 0">
<carousel :items="1" :autoplay="true" :nav="false" :dots="false">
<div v-for="(item, index) in quotes">
<div v-text="item.quote"></div>
</div>
</carousel>
</div>
<script>
import carousel from 'vue-owl-carousel';
export default {
components: { carousel },
data() {
return {
quotes: [],
};
},
mounted() {
axios.post('/api/quotes')
.then(resp => {
this.quotes = resp.data;
});
},
}

How to transfer data between components placed in different views

I have two compoennts placed in one the same view.
#extends('layouts.app')
#section('content')
<bus></bus>
<bus2></bus2>
#endsection
I want to pass data (name) from one component to other one after clicking button. To do that I' using $emit function.
/// bus component
<template>
<div>
<p> Name Bus 1{{name}}</p>
<input type="button" #click="setName()" value="s"/>
</div>
</template>
<script>
export default {
created() {},
data: function() {
return {
name: "Volvo"
};
},
methods: {
setName: function(id) {
this.$root.$emit("sname", this.name);
}
},
props: []
};
</script>
///bus 2 component
<template>
<div>
<p> Name bus 2{{name}}</p>
</div>
</template>
<script>
export default {
created() {
this.$root.$on("sname", data => {
this.name = data;
});
},
data: function() {
return {
count: 0,
name: ""
};
},
methods: {}
};
</script>
Everything works fine. Name is transfered from bus to bus2. The problem exists when I place bus2 in different view - data are not transfered but code is the same. How can I transfer data between components placed in different views
Try using Vuex to specify your app state, and mutate this when it's necessary.
Vuex states are accessible from every components using this.$store or $store.

Vuetify Snackbar leave event

I manage to implement a global Vuetify Snackbar.
My problem is to detect when the snackbar close. I read that this component support Vue transition event since 1.2. But it work only on the enter event not the leave ones.
here a fiddle for comprehension.
<transition #before-enter="beforeEnter" #before-leave="beforeLeave" #after-enter="afterEnter" #after-leave="afterLeave" #leave="leave">
<v-snackbar v-model="snackbar" top right>
Hello
<v-btn #click="snackbar = false" dark>Close</v-btn>
</v-snackbar>
</transition>
I faced the same problem and solved this way:
export default {
data: () => ({
errorMessage: '',
snackTimeout: 6000,
}),
watch: {
errorMessage() {
setTimeout(() => {
this.clearErrorMessage();
}, this.snackTimeout);
},
},
methods: {
setErrorMessage(message) {
this.snackMessage = message;
},
clearErrorMessage() {
this.snackMessage = '';
},
},
};
<template>
<v-snackbar
:value="errorMessage"
:timeout="snackTimeout"
top
>
{{ errorMessage }}
<v-btn
color="error"
flat
#click.stop="clearErrorMessage"
>
{{ 'close' }}
</v-btn>
</v-snackbar>
</template>
Define an attribute with the timeout and another with the message to show by the snackBar.
Define a function to set the message and another to clear it.
Define a watch for the message text and set a timer with the same timeout of the snackBar to clear it.
The snackBar appears only when the message is not empty.
You can use get and set methods to handle reading and updating the bound model separately.
I made a generic snackbar component that can be triggered from any other component. I'm using Vuex, vue-property-decorator and typescript here, so adjust accordingly.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<template>
<v-snackbar v-model="snackbar" max-width="100%">
<template v-slot:action="{ attrs }">
{{ text }}
<v-btn color="primary" text fab v-bind="attrs">
<v-icon dark #click="close()"> mdi-close-circle-outline </v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
#Component({})
export default class Snackbar extends Vue {
get snackbar() {
return this.$store.state.snackbar.show
}
set snackbar(show: boolean) {
this.$store.dispatch('updateSnackbar', { show, text: '' })
}
get text() {
return this.$store.state.snackbar.text
}
public close() {
this.$store.dispatch('updateSnackbar', { show: false, text: '' })
}
}
</script>

Resources