I'm try to use a computed value in a v-select (from vuetify) and when I select a value in the select there is an endless loop.
I've reproduce my dirty code in this pen to illustrate my problem. Be careful this might block your navigator.
HTML code
<div id="app">
<v-app id="inspire">
<v-card color="grey lighten-4" flat>
<v-card-text>
<v-select
v-model="select"
label="Be careful when select a value :)"
chips
tags
:items="items">
</v-select>
</v-card-text>
</v-card>
</v-app>
</div>
JS Code
new Vue({
el: '#app',
data () {
return {
obj: {
values: [{'name':'Testing'}]
},
items: [
'Programming',
'Design',
'Vue',
'Vuetify'
]
}
},
computed: {
select: {
get: function () {
return this.obj.values.map(val => val.name).sort()
},
set: function (chipsValues) {
this.obj.values = chipsValues.map(val => {return {'name': val}})
}
}
}
})
What's the proper way to code this behavior ?
A quick fix would be to validate before setting the this.obj.values whether you are getting any new values. If the new value is larger/smaller than old value, you can set it, else ignore it.
Since Javascript is synchronous, you can get away with just checking the length of the arrays.
set: function (chipsValues) {
if( this.obj.values.length != chipsValues.length) {
this.obj.values = chipsValues.map(val => {return {'name': val}})
}
}
Here's the updated pen: https://codepen.io/anon/pen/XewjdJ?editors=1010
Related
I have combobox in Vue JS to select multiple items, I want to validate if user select any items or keep it empty, but the problem when I print the length and value of combox in console always gives length == 2 and values gives undefined in both cases if user select items or not
HTML
<v-col cols="6">
<v-combobox class="xrfelements" :items="xrfElementsRatios" v-model="mainElementsRatios" :rules="notEmptyRule" label="Main Elements and Ratios" multiple required small-chips outlined dense></v-combobox>
</v-col>
java script
validateForm: function (e) {
if (document.getElementsByClassName("xrfelements").length) {
console.log(document.getElementsByClassName("xrfelements".values
return true;}
this.errors = [];
if (!document.getElementsByClassName("xrfelements").length){
console.log(document.getElementsByClassName("xrfelements").index)
console.log(document.getElementsByClassName("xrfelements".values)
this.errors.push('there is no element selected.')
}
if (this.errors.length)
{
this.$alert ("The following items should not be empty: " +this.errors.join(", "))
}
e.preventDefault(e);
},
That's not how frameworks like vue work: You often times don't interact with the DOM directly. Instead, you use Vue intermediary. Please learn the basics of Vue.
new Vue({
el: '#app',
template: ` <v-col cols="6">
<v-combobox class="xrfelements" :items="xrfElementsRatios" v-model="mainElementsRatios" :rules="notEmptyRule" label="Main Elements and Ratios" multiple required small-chips outlined dense></v-combobox>
</v-col>`,
data() {
return {
notEmptyRule: [],
mainElementsRatios: [],
xrfElementsRatios: [],
}
},
methods: {
validateForm: function (e) {
if (mainElementsRatios.length) {
console.log(mainElementsRatios);
return true;
}
this.errors = [];
if (!mainElementsRatios.length) {
console.log(document.getElementsByClassName("xrfelements").index)
console.log(mainElementsRatios)
this.errors.push('there is no element selected.')
}
if (this.errors.length) {
this.$alert("The following items should not be empty: " + this.errors.join(", "))
}
e.preventDefault(e);
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
In the following code:
JS
const App = {
template: '#app-template',
data: () => ({
selected: [],
items: Array.from({length: 50}, (x, i) => i+1).map(i => ({
id: i ,
name: `Item ${i}`,
subtitle: `Subtitle ${i}`
}))
}),
computed: {
parsedItems() {
this.selected;
return this.items.map(item => ({
someValue: 3,
...item
}));
}
}
}
new Vue({
vuetify: new Vuetify(),
render: h => h(App)
}).$mount('#app')
HTML
<script type="text/x-template" id="app-template">
<v-app>
{{selected}}
<v-container>
<v-virtual-scroll
:items="parsedItems"
:item-height="65"
height="500"
>
<template v-slot="{ item, index }">
<v-list-item-group
v-model="selected"
multiple
>
<v-list-item :key="item.id" :value="item">
<v-list-item-action>
<v-checkbox
:input-value="selected.includes(item.id)"
color="primary"
/>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>
Index: {{ index }} {{ item.name }}
</v-list-item-title>
<v-list-item-subtitle>
{{ item.subtitle }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</template>
</v-virtual-scroll>
</v-container>
</v-app>
</script>
<div id="app"></div>
When either of the checkboxes i checked or unchecked - the selected v-model always adds up another instance though it previously contains one already.
Removing this.selected; (line 16 in the Codepen below) fixes the issue.
I suspect that this.selected is somehow dereferences its own values and then can't validate the appearance of previously selected items.
Here's a Codepen with the issue at hand: https://codepen.io/MichaelKatz/pen/vYXXdgb
In my real-world scenario, I need to filter and manipulate items within the list, according to previously made selections (i.e remove / re-add items).
I do it by using a computed item property which derives its content from a previously selected items, from the selected v-model and my current solution will require me to JSON.stringify all of my objects, essentially making them value-based strings to keep things in check.
The v-model does not seem to work with Objects
<v-list-item :key="item.id" :value="item"> <!-- change this -->
<v-list-item :key="item.id" :value="item.id"> <!-- into this -->
And create a new computed property to "hydrate" those ids:
selectedItems() {
return this.selected.map(id => this.parsedItems.find(x => x.id === id))
}
Updated Codepen
It seems like accessing a v-model while filtering the items it refers to, creates a de-reference of the objects within it.
The best solution I could come up with was adding an additional computed property which will contain the logic involving this.selected.
It has indeed solved the issue for me.
computed: {
parsedItems() {
return this.items.map(item => ({
someValue: 3,
...item
}));
},
filteredItems() { // adding another computed property while using this.selected
this.selected;
return this.parsedItems;
}
}
}
From my perspective, the problem is you have used the multiple props, which will allow multiple selections.
<template v-slot="{ item, index }">
<v-list-item-group
v-model="selected"
multiple
>
Simply removing it will solve your problem.
I have a text field in which you can input the field and on Click it adds a value to an array which renders the chips. Now each chips has a value property and a toggle property. By Default every new chip has toggle set to false. Now i can add as well as delete chips. The chips are deleted by using the deleteChips method. What i am trying to do is when there is only one chip left, the chip's toggle value should change dynamically to false or true depending on whatever it was at the time. So basically toggle = !toggle.
I have tried using the array.length but wasn't able to get it working.
Here is a sample pen
Here is the sample code:-
new Vue({
el: "#app",
data() {
return {
inputValue: "",
inputArray: [],
selectedChip: ""
};
},
methods: {
createChips() {
this.inputArray.unshift({
value: this.inputValue,
toggle: false
});
this.inputValue = "";
},
deleteChips(chip) {
let index = this.inputArray.filter((el) => el.chip === chip);
this.inputArray.splice(index, 1);
},
chipSelection(item) {
this.selectedChip = item;
},
toggleChip(item) {
item.toggle = true;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<div id="app">
<v-app id="inspire">
<v-container>
<v-layout justify-center>
<v-flex xs6>
<v-text-field v-model="inputValue"></v-text-field>
<v-btn #click="createChips">Click Me</v-btn>
<div v-if="inputArray.length > 0">
<div v-for="chip in inputArray">
<v-chip :key="chip.value" close #click="chipSelection(chip.value)" #input=deleteChips(chip)>
<v-avatar>
<v-icon #click="toggleChip(chip.value)">
account_circle
</v-icon>
</v-avatar>
<span v-if="selectedChip === chip.value">{{chip.value}}</span>
</v-chip>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</v-app>
</div>
Any help will be appreciated. Thank you.
tbh, I can't really follow your intent, but based on the comments, perhaps you want to watch the inputArray and execute code based on changes to its length.
watch: {
inputArray(newValue, oldValue) {
if ((newValue.length !== oldValue.length) && (newValue.length === 1)) {
this.inputArray[0].toggle = !this.inputArray[0].toggle;
}
}
}
I am using vuetify and trying to create a method to add chips from the dropdown. Now i got most of the logic down but as you can see, the method multipleSelection does not fire off on the enter key but works fine onClick.
Here is a demo for the codepen.
So the method multipleSelection should fire off on the enter key.
new Vue({
el: '#app',
data() {
return {
arr: [],
items: [{
text: 'Foo',
value: 'foo'
},
{
text: 'Bar',
value: 'bar'
},
{
text: 'biz',
value: 'buzz'
},
{
text: 'buzz',
value: 'buzz'
}
],
}
},
methods: {
multipleSelection(item) {
this.arr.push(item)
console.log(this.arr)
},
deleteChip(item) {
console.log('delete')
this.arr = this.arr.filter(x => x !== item);
console.log(this.arr)
}
},
})
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container>
<v-combobox :items="items" label="Add Multiple Chips" multiple small-chips solo deletable-chips :value="arr">
<template v-slot:item="{ index, item }">
<v-list-tile-content #click.stop.prevent="multipleSelection(item)">
{{item.text}}
</v-list-tile-content>
</template>
<template v-slot:selection="{ index, item }">
<v-chip close dark color="info" #click:close="deleteChip(item)">
{{ item.text }}
</v-chip>
</template>
</v-combobox>
</v-container>
</v-app>
</div>
Any help will be appreciated. Thank you.
Since multipleSelection() is not being called from #keypress on v-slot:item, likely it's not where the event is being captured.
Taking a look at events on Vue Dev Tools, can see input $emit by <VCombobox> is the first one after pressing Enter.
So the following will get it, but this seems to mess with the position in the list for some reason I don't understand.
<v-combobox
...
#input.capture="(item) => multipleSelection(item)"
>
Better to add a listener,
mounted() {
this.$refs.combobox.$on('input', (items) => {
const item = items[items.length -1]; // getting all selected, so take the last
this.multipleSelection(item)
})
},
Note, I tested this on a local project with Vuetify v1.5.14.
Looks like you need #keyup
<v-list-tile-content
#keyup.enter.prevent="multipleSelection(item)" #click.stop.prevent="multipleSelection(item)">{{item.text}}
</v-list-tile-content>
Not sure about keypress....Vue docs show #keyup
https://v2.vuejs.org/v2/guide/events.html#Key-Modifiers
HTML
<v-select
v-model="selectedBank"
:items="items"
item-text="bankName"
label="Select a bank"
persistent-hint
return-object
single-line
>
</v-select>
<v-btn
round
block
color="blue darken-3"
dark
large
#click="directToBank(items.bankName)"
>
CONTINUE
</v-btn>
JS
async directToBank(bankID) {
console.log("Came into directtobank", this.selectedBank.bankName)
}
How can I get the selected value of v-select upon clicking on the button ?
. .
If you are refering to vuetify you can continue reading.
Let's take this example (codepen):
new Vue({
el: '#app',
data: () => ({
items: [
{value: '1', bankName: 'Bank1'},
{value: '2', bankName: 'Bank2'},
],
selectedBank: null
}),
methods: {
directToBank() {
console.log("Label: ", this.selectedBank.bankName)
console.log("Value: ", this.selectedBank.value)
}
}
})
If you use other key for value in your items object you need to specify the item-value attribute in your v-select element, else it will use the "value" key by default.
More on v-select component
When you use return-object, you are bringing selectedBank into data() hence you will only need to call this.selectedBank.something inside your your #click function in the button.
Getting values from vuetify select is similar to getting the values for an even fired in javascript.
passing the even as a prop and the prop is the value you want
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: {
items: ['Foo', 'Bar', 'Fizz', 'Buzz'],
},
methods: {
select_value(e) {
console.log(e)
}
}
})
<v-select :items="items" label="Solo field" #change="select_value" solo></v-select>