I am testing Vuetify 3 (npm:#vuetify/nightly#next) v-select and try to get change event. However, my handler function is not called. This is my code:
TS:
export default defineComponent({
setup() {
function onLanguageChange(a: any) {
console.log(a);
}
const items = ['Русский', 'English'];
return {onLanguageChange, items}
}
});
Vue
<v-select
prepend-icon="mdi-web"
:items="items"
label="Lang"
#change="onLanguageChange"
></v-select>
And this is what I get in console when I focus select and change its value.
Could anyone say, if this is a bug or something is wrong with my code (and how to fix it)?
v-select's event list does not include the change event. It only has one event: update:modelValue.
Use the update:modelValue event instead:
<v-select #update:modelValue="onLanguageChange">
demo 1
Or use a v-model with a watcher:
<v-select v-model="lang">...</v-select>
import { ref, watch } from 'vue'
export default {
setup() {
const lang = ref()
watch(lang, (newValue) => console.log('new lang', newValue))
return { lang }
}
}
demo 2
Related
I have a small piece of JavaScript code that is used to toggle a repsonsive navigation menu.
const toggleNavigation = document.getElementsByClassName('navigation-icon')[0]
const navbarLinks = document.getElementsByClassName('navbar-links')[0]
toggleNavigation.addEventListener('click', () => {
navbarLinks.classList.toggle('active')
})
I want to rewrite this for Vue. I have done event handling in Vue before, but it is difficult for me to relate the examples in the guide to this particular piece of code.
What I tried was to use the v-on directive:
<template>
<div class="navigation-icon" #click="toggleNavigation">
<i class="pi pi-bars"></i>
</div>
</template>
<script>
export default {
setup() {
const toggleNavigation = document.getElementsByClassName('navigation-icon')[0]
const navbarLinks = document.getElementsByClassName('navbar-links')[0]
if (toggleNavigation) () => {
navbarLinks.classList.toggle('active')
}
return {
toggleNavigation,
navbarLinks
}
}
}
</script>
What is the correct way to write this?
In your case toggleNavigation is not a function, so it make no sense to write: #click="toggleNavigation"
Please take a look at the handling event reference from the Vue.js V3 docs: https://v3.vuejs.org/guide/events.html#event-handling
You can do something like this:
<template>
<div class="navigation-icon" #click="toggleNavigation">
<i class="pi pi-bars"></i>
</div>
</template>
<script>
export default {
methods: {
toggleNavigation() {
// Handle the toggle logic here
}
}
}
</script>
Now if you want to emit something out of this component to a parent component, use the event emitter inside the toggleNavigation() method. More on this here.
toggleNavigation() {
this.$emit('toggled')
}
In the parent component, catch the emitted event like this:
<template>
<ChildComponent #toggled="toggled" />
</template>
<script>
export default {
methods: {
toggled() {
// Handle the toggle logic here
}
}
}
</script>
FYI: You can choose to pass optional params too while emitting, just add them like this: this.$emit('toggled', param1, param2).
I'm using vuetify, and I have a tooltip over a button.
I doesn't want to show the tooltip on hover nor on click, I want to show the tooltip if some event is triggered.
translate.vue
<v-tooltip v-model="item.showTooltip" top>
<template v-slot:activator="{}">
<v-btn #click="translateItem(item)"> Call API to translate</v-btn>
</template>
<span>API quota limit has been reached</span>
</v-tooltip>
<script>
export default(){
props: {
item: { default: Objet}
}
methods: {
translateItem: function (item) {
axios
.post(baseURL + "/translateAPI", {
text: item.originTrad;
})
.then((res) => {
if (apiQuotaLimitReached(res) {
// If limit is reached I want to show the tooltip for some time
item.showTooltip = true;
setTimeout(() => {item.showTooltip = false;}, 3000);
} else { ..... }}}
</script>
itemSelect.vue (where I create object item and then use router push to transmit it to the translation page)
<script>
export default(){
methods: {
createItem: function () {
item.originTrad = "the text to translate"
....
item.showTooltip = false;
this.$router.push({
name: "translate",
params: {
"item": item,
},
}); }}
</script>
As you can see I removed the v-slot:activator="{ on }" and v-on="on" that I found on the exemple:https://vuetifyjs.com/fr-FR/components/tooltips/ , because I don't want to show the tooltip on hover. But It doesn't work as expected, the tooltip is not showing properly.
Some help would be great :)
For starters, you are trying to change a prop in the child component, something that you should not do:
[Vue warn]: Avoid mutating a prop directly since the value will be
overwritten whenever the parent component re-renders. Instead, use a
data or computed property based on the prop's value. Prop being
mutated: "item.showTooltip"
So start by making a separate data variable for showTooltip (doesn't need to be a property of item perse) and try setting it to true to see what happens (and of course change v-model="item.showTooltip" to v-model="showTooltip" on v-tooltip)
I am building a nuxt app with Vuetify. My use case is that i want to implement a global bottom-sheet.
My code below works fine until when i click outside the sheet and it throws an error.
What am I missing below?
Error: [vuex] do not mutate vuex store state outside mutation handlers.
What i have tried so far.
<template>
<div>
<v-bottom-sheet v-model="$store.state.sheet">
<v-card>
<v-card-title>Hi there</v-card-title>
<v-card-subtitle>This is a demo sheet</v-card-subtitle>
</v-card>
</v-bottom-sheet>
<v-btn class="mt-5" #click="openSheet">Sheet</v-btn>
</div>
</template>
<script>
export default {
methods: {
openSheet() {
this.$store.commit("openSheet");
}
}
};
</script>
Store/index.js
export const state = () => ({
sheet: false
})
export const mutations = {
openSheet(state){
state.sheet = !state.sheet
}
}
When you clicked outsise, you tried to modify the state directly. You need to mofify it with a mutation, one way is to create a computed property and set it to v-model:
<template>
<div>
<v-bottom-sheet v-model="sheet">
<v-card>
<v-card-title>Hi there</v-card-title>
<v-card-subtitle>This is a demo sheet</v-card-subtitle>
</v-card>
</v-bottom-sheet>
<v-btn class="mt-5" #click="openSheet">Sheet</v-btn>
</div>
</template>
<script>
export default {
computed: {
sheet: {
get () {
return this.$store.state.sheet;
},
set (value) {
this.$store.commit("openSheet");
}
}
},
methods: {
openSheet() {
this.$store.commit("openSheet");
}
}
};
I wish to listen to an 'esc' key event in order to call a method in a Vue component.
The docs shows this example:
<input v-on:keyup.enter="submit">
but i'm using a <div></div>, and need to catch the event from outside.
However, I wish NOT to overload global handlers or anything like that.
Any suggestions?
For anyone who wanders here from google, in Vue 2...
<div #keydown.esc="something_in_your_methods"></div>
The secret for making keydown events work on divs and other non-focusable elements is to add a tabindex attribute:
<div tabindex="0"
#keydown.left="previousImage"
#keydown.right="nextImage" />
Now the div has become a focusable element and the key events will be triggered.
Here is more info on focusable elements and tabindex
What I did was go for a mixin.
The mixin in a file called close.js
export default {
created() {
let that = this;
document.addEventListener('keyup', function (evt) {
if (evt.keyCode === 27) {
that.close();
}
});
},
};
Import and use it in the desired component
import closeMixin from './../../mixins/close.js';
export default {
mixins: [closeMixin],
props: [],
computed: {}
methods: {
close(){
// closing logic
}
}
}
3 things to make sure of on the main element:
It has a tabindex
It or a descendant is focused
It is listening for the event
Here is how I usually manage my modals:
<div ref="modal" #keyup.esc="close" tabindex="-1">
<!-- Modal content -->
</div>
mounted() {
this.$refs.modal.focus();
}
In my case, I created a directive, v-esc.ts. (※ This is Vue3 directive writing way)
import { Directive } from 'vue'
const directive: Directive = {
beforeMount(el, binding) {
el._keydownCallback = (event) => {
if (event.key === 'Escape') {
binding.value()
}
}
document.addEventListener('keydown', el._keydownCallback)
},
unmounted(el, binding) {
document.removeEventListener('keydown', el._keydownCallback)
delete el._keydownCallback
}
}
export const esc = { esc: directive }
Then I can use it in any component like this.
(NOTE: you must pass a function param to v-esc, because the param executed as binding.value() in the directive)
<template>
<img
#click.prevent="close"
v-esc="close"
src="#/assets/icons/close.svg"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { esc } from '#/common/directives/v-esc'
export default defineComponent({
name: 'nitCloseButton',
...
methods: {
close() {
this.$emit('close')
}
},
directives: {
...esc
}
})
</script>
P.S
One month after, I also need arrow left and arrow right keys.
So, I've made this directive more general like this.
import { Directive } from 'vue'
const directive: Directive = {
beforeMount(el, binding) {
el._keydownCallback = event => {
console.log('keydown', event.key)
if (event.key === binding.arg) {
binding.value()
}
}
document.addEventListener('keydown', el._keydownCallback)
},
unmounted(el, binding) {
document.removeEventListener('keydown', el._keydownCallback)
delete el._keydownCallback
}
}
export const keydown = { keydown: directive }
You can detect any key's keydown by passing keyname as binding.args
(v-keydown:{keyName} like below)
<button
v-keydown:ArrowLeft="moveToPreviousPage"
class="controller-button lo-center"
#click="moveToPreviousPage"
>
<arrow-icon :rotation="180" />
</button>
<button
v-keydown:ArrowRight="moveToNextPage"
class="controller-button lo-center"
#click="moveToNextPage"
export default defineComponent({
name: 'componentName',
directives: {
...keydown
}
...
})
You can't. Key events are dispatched from the body tag and Vue can't be mounted to the <body> tag.
]
You'll have to set up your own event listener.
(image source & more info at When VueJS Can't Help You)
I looked at several questions to figure out what I am
doing wrong. Everything looks to me setup correctly.
GOAL
Based upon the value of COMPONENT A change hide/display content using v-show in DEPENDENT COMPONENT.
PROBLEM
Inside TextField Component, there is an input that triggers a mutation on my vuex store. Dependent Component has a computed value that listens to changes on the vuex store.
When typing in my TextField Component, I can verify by using the Vue.js extension that the mutations are triggering as expected.
HOWEVER, there is no change on the page.
COMPONENT A
<template>
<div class="field">
<input type="text" :name="field.name" v-bind:value="value" v-on:input="updateValue($event.target.value)">
</div>
</template>
<script>
export default {
props: ['field'],
methods: {
updateValue: function (value) {
this.$store.commit('UPDATE_USER_INPUT', {'key': this.field.name, 'value': value})
}
}
}
</script>
MUTATION
UPDATE_USER_INPUT (state, payload) {
state.userInput[payload['key']] = payload['value']
}
DEPENDENT COMPONENT
<template>
<div class="field-item">
{{ userInput }}
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState([
'userInput'
])
}
}
</script>
No matter what I try, {{ userInput }} remains empty: {} until I navigate my route back to the same location. But there is no computed value listener being triggered.
If you are setting a new key within the vuex state then you will need to use:
UPDATE_USER_INPUT (state, payload) {
Vue.set(state.userInput, payload.key, payload.value)
}
Otherwise it won't be reactive.
See documentation.