How can I change the selected v-select item using a method? - javascript

I am trying to set the selected value of my v-select component in a method in the script part.
These are the the code parts:
<v-flex xs4>
<v-select v-model="selected" :items="items" item-text="name"
item-value="value" outlined class="ml-2 mr-1" return-object></v-select>
</v-flex>
and the part of the script:
export default {
return{
data: function () {
items: [
{ name: 'item 1', value: 1 },
{ name: 'item 2', value: 2 },
{ name: 'item 3', value: 3 }],
selected: { name: 'iteam 1', value: 1 }
},
methods: {
apply (component) {
for (var i in this.items.entries()) {
if (i.name === component.item) {
this.selected.name = i.name
this.selected.value = i.value
}
}
}
}
}
}
I've tried different versions like
this.selected = i
this.selected[name] = i.name
this.selected[value] = i.value
this.selected = { i.name, i.value }
but nothing is working.

It's and old question but Let me post my answer to help others as well, after alot of search I have come to this point, and I want to share it with others as well.
//This will load all your items in dropdown
<v-select
v-model="selected"
:items="items"
item-text="name"
item-value="value"
label="Select Item"
dense>
</v-select>
Now Edit Part
Lets say you want to edit a record, so you will probably pass the record id in edit method of your vuejs then inside edit method of vuejs, you will do an edit axios call for that specific record to first show it inside fields and then you will update. But before update you need to do something inside edit method of your vuejs when you will have already loaded data for that specific id.
Now lets say you have received a record from database according to a specific id, you will see a dropdown id I mean to say a foreign key for a dropdown that you had saved during storing data.
Suppose you have items array which holds whole data for a dropdown. Inside this you are having an value and name fields. So you have this items array and an object from database during edit for a specific record. Now you are good to go with below code.
Inside Edit Method of vuejs
//item_id it is like a foreign key for dropdown you have in your table
this.selected = this.items.find(item => item.value === parseInt(res.data.item_id))
this.selected = parseInt(this.selected.item_id)
Note: I am doing parseInt() it's because when you check in console the primary key is an integer like 1 but foreign key like rating_id is string i-e "1". You can also use two equals == if you are not using parseInt() but I haven't checked that.
For clearly understanding I am just sharing a sample edit code which might help you a bit
editItem(id){
axios.get( `/api/category/${id}` ).then( res => {
if(res.data.status === 200){
console.log(res.data.data)
this.name = res.data.data.name
this.id = res.data.data.id
this.parent_id = this.list_categories.find(item => item.id === parseInt(res.data.data.parent_id))
this.parent_id = parseInt(this.parent_id.id)
this.edited = true
}else{
this.$toaster.error( res.data.message )
}
});
}

Here's a complete example:
new Vue({
el: '#app',
data () {
const items = [
{ name: 'item 1', value: 1 },
{ name: 'item 2', value: 2 },
{ name: 'item 3', value: 3 }
]
return {
items,
selected: items[1]
}
},
methods: {
apply (component) {
this.selected = this.items.find(item => item.name === component.item)
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<link href="https://unpkg.com/vuetify#1.5.16/dist/vuetify.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#1.5.16/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-flex xs4>
<v-btn
v-for="item in items"
:key="item.value"
#click="apply({item: item.name})"
>
{{ item.name }}
</v-btn>
<v-select
v-model="selected"
:items="items"
item-text="name"
item-value="value"
outlined
class="ml-2 mr-1"
return-object
></v-select>
</v-flex>
</v-app>
</div>
In your original code the apply method seemed to be expecting to be passed an object (component) with an item property that matched the name of one of the items. I've tried to preserve that behaviour though it isn't clear why you would want that. Typically the value used for item-value would be the underlying id value used behind the scenes, not the item-text.
Rather than trying to copy values around between objects I've treated the 3 values in items as canonical and ensured that selected is always set to one of those 3. Not merely an object with the same values but actually one of those objects. This isn't strictly required but it seemed like the easiest way to me.

You need to set selected all at once, like this.selected = { name: 'item 3', value: 3 }
I changed the parameter type of apply for testing to string but it should look something like:
apply (component) {
temp={}
this.items.forEach((i)=> {
if (i.name === component) {
temp.name = i.name;
temp.value = i.value;
}
});
this.selected=temp
}
and I called apply with a btn
<v-btn v-for="n in 3" #click="apply(`item ${n}`)">Apply {{n}}</v-btn>

Related

Vue 3 custom checkbox component with v-model and array of items

Desperately in need of your help guys.
So basically I have a custom checkbox component whit a v-model. I use a v-for loop on the component to display checkboxes with the names from the array. In the parent component I have two columns Available and Selected. The idea is that if I check one of the boxes in the Available column it should appear on the Selected column. The problem is that it displays letter by letter and not the full name.
I am able to achieve the desired result without having a checkbox component, but since I will be needing checkboxes a lot throught my project I want to have a component for it.
Please follow the link for the code:
CodeSandBox
Dont mind the difference in styling.
The problem:
The desired outcome:
There are two problems. The first problem is, that you have your v-model set to v-model="filter.filterCollection", so a checkbox you select will be stored into the array as a string and if you select another checkbox the string gets overwritten. The second problem is, that you call that stored string as an array. That causes, that your string, which is an array of letters, will be rendered for each letter. So 'Number' is like ["N", "u", "m", "b", "e", "r"].
To solve your problem, you need to store every selection with its own reference in your v-model. To cover your needs of correct listing and correct deleting you need to apply the following changes:
Your checkbox loop
<Checkbox
v-for="(item, index) in items"
:key="item.id"
:label="item.name"
:id="index"
:isChecked="isChecked(index)" // this is new
#remove-selected-filter="removeSelectedFilter" // This is new
:modelValue="item.name"
v-model="filter.filterCollection[index]" // Change this
/>
Your v-model
filter: {
filterCollection: {} // Object instead of array
}
Methods in FilterPopUp.vue
methods: {
removeSelectedFilter(index) {
delete this.filter.filterCollection[index];
},
isChecked(index) {
return !!this.filter.filterCollection[index];
}
}
Your Checkbox.vue:
<template>
<label>
<p>{{ label }}</p>
<input
type="checkbox"
:id="id"
:value="modelValue"
:checked="isChecked"
#change="emitUncheck($event.target.checked)"
#input="$emit('update:modelValue', $event.target.value)"
/>
<span class="checkmark"></span>
</label>
</template>
<script>
export default {
name: "Checkbox",
props: {
modelValue: { type: String, default: "" },
isChecked: Boolean,
label: { type: String },
value: { type: Array },
id: { type: Number },
},
methods: {
emitUncheck(event) {
if(!event){
this.$emit('remove-selected-filter', this.id);
}
}
}
};
</script>
This should now display your items properly, delete the items properly and unselect the checkboxes after deleting the items.
StevenSiebert has correctly pointed to your errors.
But his solution is not complete, since the filters will not be removed from the collection when you uncheck one of them.
Here is my complete solution of your checkbox working as expected:
Checkbox.vue
<template>
<label>
<p>{{ label }}</p>
<input
type="checkbox"
:id="id"
v-model="checked"
#change="$emit('change', { id: this.id, checked: this.checked})"
/>
<span class="checkmark"></span>
</label>
</template>
<script>
export default {
name: "Checkbox",
props: {
modelValue: { type: Boolean, default: false },
label: { type: String },
id: { type: Number },
},
emits: ["change"],
data() {
return {
checked: this.modelValue
};
}
};
</script>
FilterPopUp.vue
<template>
...
<Checkbox
v-for="(item, index) in items"
:key="index"
:label="item.name"
:id="index"
#change="onChange"
/>
...
</template>
<script>
...
methods: {
removeSelectedFilter(index) {
this.filter.filterCollection.splice(index, 1);
},
onChange(args) {
const {id, checked} = args;
const item = this.items[id].name;
if (checked) {
if (this.filter.filterCollection.indexOf(item) < 0) {
this.filter.filterCollection.push(item);
}
} else {
this.filter.filterCollection = this.filter.filterCollection.filter( i=> i != item);
}
},
},
...
Here is the working CodeSandbox:
https://codesandbox.io/s/pensive-shadow-ygvzb?file=/src/components/Checkbox.vue
Sure, there are many ways to do it. If somebody has a nicer and shorter way to do it, please post your solution. It will be interesting to look at it.

Vue updating all components in v-for

In my component the v-for displays all items in the allItems array brought from firestore.
Add the end of there as an Add button so that the user can add an item to his basket for that I have an array set up in store set up in which the items get added to.
I have a v-if on that button show the message Item Added when clicked for that particular item in which itemAdded becomes true. So that the Item Added message can be shown and the button dissapears.
But when I click the button all the items show the message Item Added.
I want only the particular item to show the message. Can anyone suggest a solution for this.
The Component
<v-col cols="12" sm="6" md="3" v-for="(item) in allItems" :key="item.ITEM_NAME">
<v-card
class="d-flex flex-row disableScroll"
flat
tile
style="margin-top: 16px; padding: 2px; overflow-x: auto;">
<v-card max-width="250">
<v-img
class="white--text align-end"
height="200px"
src="../../assets/img/category_blank.jpg"
>
<v-card-title>{{item.ITEM_NAME}}</v-card-title>
</v-img>
<v-card-subtitle class="pb-0">{{item.ITEM_DESC}}</v-card-subtitle>
<v-card-actions>
<v-card-text>Rs {{item.ITEM_PRICE}}</v-card-text>
<v-btn v-if="item.ITEM_STOCK && !itemAdded" style="margin-left: auto" color="orange" text #click="addItem(item)">
Add
</v-btn>
<p v-else-if="itemAdded">Item Added</p>
<p v-else> Out of Stock</p>
</v-card-actions>
</v-card>
</v-card>
</v-col>
The Script
<script>
import {db, getStoreID} from "#/main";
export default {
data() {
return {
allItems: [],
orderItems: {},
itemAdded: false,
}
},
name: "HomeCategories",
props: ["category"],
created() {
let docID = getStoreID();
db.collection("STORES")
.doc(docID)
.collection("ITEMS")
.doc("DATA")
.collection(this.$props.category).get().then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.allItems.push(doc.data())
})
})
},
methods: {
addItem(item){
this.orderItems.name = item.ITEM_NAME,
this.orderItems.price = item.ITEM_PRICE,
this.orderItems.isAvail = item.ITEM_AVAILABLE,
this.orderItems.imgUrl = item.ITEM_IMG_URL,
this.orderItems.quantity = 1,
this.itemAdded = true
this.$store.commit("ADD_ITEM_TO_BASKET", this.orderItems);
}
}
};
</script>
When you are checking condition instead of itemAdded as boolean, make it as an Array
create a new function in methods like this
isItemAdded(value) {
if (this.itemAdded.includes(value)) {
return true;
}
return false;
}
now, in the addItem function add the value to array this.itemAdded like this
this.itemAdded.push(item);
Changes in the HTML
<v-btn v-if="item.ITEM_STOCK && !isItemAdded(item)" style="margin-left: auto"
color="orange" text #click="addItem(item)">
Add
</v-btn>
<p v-else-if="isItemAdded(item)">Item Added</p>
And you are all set. Cheers
Your itemAdded value is boolean, so it has only 2 values: true and false, but you use it for every item, so if you change it then you change it for every item.
This option for only 1 item can be added.
You should be more precise. For example, you can keep item name in itemAdded, so you will always know what item was added. In that case only one item can have this flag.
This option if every item can be added once.
Another option is to treat itemAdded as array, so every item can be added (once).
First, change component data to have itemsAdded to be array:
...
data() {
return {
allItems: [],
orderItems: {},
itemAdded: [], // <--- here we change itemAdded to be array
}
},
...
Now we need to check itemAdded not as boolean, but as array of boolean:
...
<v-btn v-if="item.ITEM_STOCK && !itemAdded[item.ITEM_NAME]"
...
<p v-else-if="itemAdded[item.ITEM_NAME]">Item Added</p>
...
And finally we change the flag for item on button click:
...
methods: {
addItem(item){
...
this.itemAdded[item.ITEM_NAME] = true; // <--- see array here?
...
If you need, I can add some code examples.

How to re-render an HTML element after a value deep in an multi-dimension array changes?

I have a v-btn whose content is determined by values in the deepest layer of a multi-dimension array.
<div v-for="student in students">
<v-btn disabled v-for="tag in student.tags">{{tag}}</v-btn>
</div>
Here tags is a sub-array.
I want to re-render this button after the values change, but don't know how.
I have already used Vue.set like:
// "this.students" is an array, and "this.students[index].tags" is a sub-array.
// I increment the length of the sub-array.
this.students[index].tags.length++;
// Then add a new value into the sub-array at the end.
Vue.set(this.students[index].tags, this.students[index].tags.length - 1, value)
By printing out to the console, I can see both the values and the length of the sub-array, this.students[index].tags, change, and there should be a new button appear because I added a new value into this.students[index].tags, but there is not. And only after I re-compile the client end, the new button show up.
Could anyone teach how to re-render that button?
Thanks in advance!
Vue only observes the object's own properties - that is, only 1 level deep, no more. So you can try one of these:
use this.$forceUpdate(); (https://v2.vuejs.org/v2/api/#vm-forceUpdate)
use this.$set(this.students[index], 'tags', this.students[index].tags.concat([value])); - once set, the tags array will be observed by Vue so you can use tags.push() on subsequent additions
use a hash-map for students' tags
computed:
{
studentTags()
{
const result = {};
this.students.forEach(student =>
{
result[student.id] = student.tags;
});
return result;
}
},
methods:
{
addTag(studentId, tag)
{
this.studentTags[studentId].push(tag);
}
}
We do not need to use Vue.set to push the new data in an array or sub-array. It will auto-handle by the vuejs.
However, we should use Set to reflect the updates in a sub-array.
See this example-
<template>
<div id="app">
<div v-for="(student, index) in students" :key="index">
<button #click="addMoreTag(student.id)" style="background: green">
Add more
</button>
<button
v-for="(tag, tindex) in student.tags"
:key="tindex + 't'"
#click="updateTag(student.id, tag)"
>
{{ tag }}
</button>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
students: [
{
id: 1,
name: "John",
tags: ["one", "two"],
},
{
id: 2,
name: "Mira",
tags: ["three", "five"],
},
],
};
},
methods: {
addMoreTag(id) {
let index = this.students.findIndex((item) => item.id === id);
this.students[index].tags.push("new");
},
updateTag(student_id, tag) {
let index = this.students.findIndex((item) => item.id === student_id);
let tagIndex = this.students[index].tags.findIndex(
(item) => item === tag
);
this.$set(this.students[index].tags, tagIndex, "updated");
},
},
};
I wrote in coodepen too- https://codesandbox.io/s/blissful-cdn-5kyt1c?file=/src/App.vue

Vuejs: How to make this if else as Boolean?

I have a component with buttons that show some elements (v-autocomplete) when we click on them, but I should make it with a Boolean to simplify the code, how can I do that?
Because actually, it adds the index of the item in one array in showCompetence state, but I just would like a Boolean on each index Basically at the "openCompetence" function in "Methods".
import { mapGetters, mapActions } from "vuex";
export default {
name: "SkillCvCard",
data() {
return {
selectedCompetence: []
}
},
updated() {
this.addSelectSkill(this.userCompetences.competences.list);
console.log(this.selectedSkills)
},
props: {
userCompetences: {
type: Array
},
showCompetence: {
type: Array
}
},
computed: {
...mapGetters(["selectedSkills"]),
console: () => console,
},
methods: {
...mapActions(['addSelectSkill']),
openCompetence(index) {
if (this.showCompetence.includes(index)) {
console.log("close")
this.showCompetence.splice(this.showCompetence.indexOf(index), 1)
} else {
this.showCompetence.push(index)
console.log("open")
}
console.log(this.showCompetence)
}
}
about the template I will just add the minimum I hope it will be ok:
The first is where we click, to launch the "openCompetence" function.
<div v-for="(competences, index) of userCompetences.competences" :key="index">
{{ competences.category }} <v-btn #click="openCompetence(index)"> Add </v-btn>
</div>
the rest is a v-container with a v-for including multiple v-autocomplete, but the most important inside is the v-if:
<div class="skill-field" v-for="(skill, index) of userCompetences.competences" :key="index">
<template>
<v-flex md12 sm12 xs12 v-if="skill.list.length>0">
<v-autocomplete
v-if="showCompetence.includes(index)"
v-model="userCompetences.competences.list"
:items="skill.list"
chips
hide-selected
:label="skill.category"
item-text="name"
item-value="name"
multiple
>
</v-autocomplete>
</v-flex>
</template>
</div>
Normally, you would use a computed property to solve a problem like this. However, because you are inside of a v-for, a computed property is a little more difficult since you can't evaluate it in the context of each value of the v-for's index.
There are two common options:
Do exactly what you are doing. It's actually no less efficient since it will not re-evaluate unless data it depends on changes.
Add a computed called something like competencesDisplayState that returns an array of booleans that matches the ordering of the userCompetences.competences array in the v-for. Then your v-if can become something like:
v-if="competencesDisplayState[index]"
I normally just opt for the first approach as it is simpler and is easier to read and maintain.
If you want to go route 2, here's some code for the competencesDisplayState computed:
competencesDisplayState: () => {
var result = [];
for (var index=0;index < this.userCompetences.competences.length;++index) {
result(this.showCompetence.includes(index));
}
return result;
}

How can I get the value of v-select field on form submit?

I am trying to get the value of a select field in my Vue project. I have tried several things but a few of the things I still have links for are:
https://codepen.io/johnjleider/pen/jwoNrG
Get selected value in dropdown list using JavaScript?
I still haven't been able to get the value select. Is there anyway I can capture the selected value in the FormData object? I would think this would make it the easiest to grab.
In template:
<v-form #submit.prevent="update" id="exapi">
<v-select
id="select"
name="select"
v-bind:items="selectOptions"
v-model="selectedOption"
label="Exchange"
autocomplete
>
</v-select>
<v-text-field
name="input-api-input"
label="Enter key"
hint="Key"
v-model="password"
:append-icon="e1 ? 'visibility' : 'visibility_off'"
:append-icon-cb="() => (e1 = !e1)"
:type="e1 ? 'password' : 'text'"
>
</v-text-field>
<v-btn
color="secondary"
:loading="loading"
#click.native="loader = 'loading'"
:disabled="loading"
type="submit"
>
Change API Keys
</v-btn>
</v-form>
In script I have tried multiple methods but still no result:
updateExchangeApi() {
const form = new FormData(document.getElementById('exapi'));
const key = form.get('input-api-input');
const selectValue0 = form.get('select')
const e = document.getElementById('exchange-select');
const selectValue1 = e.options[e.selectedIndex].text;
console.log('in updateExchangeApi()');
// console.log(exchange);
console.log(key);
}
Is there a best practice I am missing? Any help/advice would be greatly appreciated.
UPDATE:
I forgot to include that I did try setting v-model="selectedOption" with selectedOption in data but still not able to get the value:
data: () => ({
loader: null,
LineChart,
BarChart,
UserTable,
selectedOptions: [],
selectedOption: '',
})
But when I log selectedOption form my form submit function I get undefined? console.log(self.selectedOption);
Instead of using FormData you can create a form object in vue data function.
Inside form object you can declare all the properties that will have the values of your form inputs.
Then you can access the form object on your submit function using this.form
Example Below:
<template>
<v-form #submit.prevent="submit">
<v-select
v-model="form.selectedItems"
:items="items"
multiple
label="Select"
item-value="value.foo"
item-text="text"
></v-select>
<v-text-field v-model="form.text"></v-text-field>
<v-btn type="submit">submit</v-btn>
</v-form>
</template>
<script>
export default {
data: function () {
return {
items: [
{ text: "Item 1", value: { foo: "item", bar: 2 } },
{ text: "Item 2", value: { foo: "test", bar: 42 } },
{ text: "Item 3", value: { foo: "foobar", bar: 4 } },
],
form: {
selectedItems: [],
text: "",
},
};
},
methods: {
submit() {
const formData = this.form;
// log the form data
console.log(formData);
//... handle data here
}
}
}

Categories

Resources