I'm trying to build a dropdown menu for my Vue3 project to select a country from list. Before that I've been using select>option to list and select country like this;
<template>
<select
class="settings-item settings-text upper shadow-lg shadow-gray-300/50"
v-model="selectedCountry"
:disabled="!countries.length"
>
<option v-for="country in countries" :key="country.id" :value="country">
{{ country.name }}
</option>
</select>
</template>
computed: {
selectedCountry: {
get() {
return this.userCountry
},
set(country) {
this.SET_USER_COUNTRY(country)
this.SET_COUNTRY_ID(country.id)
this.fetchCities()
}
}
}
But now I have a new component that gets and renders the list, I'm sending selected country data from grandchild component to this parent component now I want to send the data comes from child components and update my computed property again.
I think I may write a mehtod to do mutation and fetch cities, but I'm not sure.
Related
I am teaching myself vue 3. I have read article after article on v-model and each time I think I understand how it works I get confused again.
My goal: I built a custom dropdown component. I need the ability to control the value of this dropdown from the parent. When the dropdown changes I want to let the parent know the new value and the index.
Child component.vue
<div>
<select
:value="modelValue"
#input="$emit('update:modelValue', $event.target.value)"
>
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
props: ["options", "modelValue"],
emits: ["update:modelValue"],
methods: {
selected() {
//??????
//want to emit this to the parent
let selectedIndex = this.$event.target.selectedIndex + 1
//this.$emit(value, selectedIndex)
},
},
};
</script>
parent.vue
<template>
<my-drop-down :options="options" v-model="selectedOption" />
</template>
<script>
import myDropDown from "./components/base_dropdown.vue";
export default {
name: "App",
data: () => ({
selectedOption: "2 Line",
selectedIndex: 0,
options: ["1 Line", "2 Line", "3 Line"],
}),
components: {
myDropDown,
},
methods: {
//How can I call this when the select value changes??
onSelectChange(selected, index) {
console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
},
},
};
</script>
The two way binding is working correctly. I can control the value of the dropdown from either the child or the parent. But how do I call the onSelectChange method in my child component
Also, and this is may be a dumb question but...
v-model="selectedOption" is the same as writing :value="modelValue" #input="$emit('update:modelValue', $event.target.value)"
so why is the parent written like this <my-drop-down :v-model="selectedOption" />
and the child written like this <select :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
and not simply
<select :v-model="selectedOption />
If you want to call a method inside your parent component when the "select value changes", It is better to call it inside a Vue watch like the codes below:
Parent component:
<template>
<my-drop-down :options="options" v-model="selectedOption" />
</template>
<script>
import myDropDown from "../components/baseDropdown.vue";
export default {
name: "parentModel",
data: () => ({
selectedOption: "2 Line",
// selectedIndex: 0,
options: ["1 Line", "2 Line", "3 Line"],
}),
components: {
myDropDown,
},
computed: {
/* It is better to use computed property for "selectedIndex", because it is related to "selectedOption" and changes accordingly. */
selectedIndex: function () {
return this.options.indexOf(this.selectedOption)
}
},
watch: {
selectedOption(newSelect, oldSelect) {
this.onSelectChange(this.selectedOption, this.selectedIndex)
}
},
methods: {
//How can I call this when the select value changes??
onSelectChange(selected, index) {
console.log(`Parent L3rd Change, name: ${selected}, index: ${index} `);
},
},
}
</script>
<style scoped>
</style>
Child component:
<template>
<div>
<select
:value="modelValue"
#change="$emit('update:modelValue', $event.target.value)"
>
<!-- You can use v-model also here. But it only changes the value of "modelValue" and does not emit anything to parent component. -->
<!-- <select v-model="modelValue">-->
<option v-for="option in options" :key="option">
{{ option }}
</option>
</select>
</div>
</template>
<script>
export default {
name: "baseDropdown",
props: ["options", "modelValue"],
emits: ["update:modelValue"],
/* --------------------------------- */
/* You don't need this method, because "$emit('update:modelValue', $event.target.value)" that is used in "select" tag itself is enough to emit data to the parent component. */
/* --------------------------------- */
// methods: {
// selected() {
//
// //??????
// //want to emit this to the parent
// let selectedIndex = this.$event.target.selectedIndex + 1
// //this.$emit(value, selectedIndex)
// },
// },
}
</script>
<style scoped>
</style>
And about your second part of the question:
v-model="selectedOption" is the same as writing :value="modelValue" #input="$emit('update:modelValue', $event.target.value)"
In my opinion it is not a true statement for two reasons:
Reason one: according to Vue docs :
v-model="selectedOption" is the same as writing :value="selectedOption"
#input="event => selectedOption = event.target.value"
you can't see any $emit in the above statement. But in your case you want to emit data to the parent component.
Reason two: again according to Vue docs it is better to use change as an event for <select> tag.
You look to be needing a watcher in your parent component, one that watches for changes to the selectedOption property, and then uses the new value to get the index from the options array and adds one to it, and uses the new value to set the selectedIndex property.
Per the Vuejs API section on Watchers:
Computed properties allow us to declaratively compute derived values. However, there are cases where we need to perform "side effects" in reaction to state changes - for example, mutating the DOM, or changing another piece of state based on the result of an async operation.
With Options API, we can use the watch option to trigger a function whenever a reactive property changes.
So, for your code, it might look something like:
watch: {
selectedOption(newValue, oldValue) {
console.log('In watcher. New value: ' + newValue)
// get the index of the String in the array and use it to set
// the selectedIndex:
this.selectedIndex = this.options.findIndex(i => i === newValue) + 1;
console.log('selectedIndex: ' + this.selectedIndex)
}
},
As for your question's "second part",
why is the parent written like this <my-drop-down :v-model="selectedOption" />
and the child written like this <select :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
and not simply <select :v-model="selectedOption />
It is probably best to ask that as a separate question, in order to maintain question specificity as required by the site, but as I see it, selectedOption is not acting as a model for the select tag, and in fact selectedOption isn't even a property of the child component, nor should it be.
Well, I am trying to get the selected value of a select. This is the html code for the select:
<select id="employee">
<option v-bind:key="employee" v-for="employee of employees" v-bind:value="{id: employee.id}">{{ employee.name }}</option>
</select><br><br>
This is the JavaScript code where I would like to print the value of the select to the console.
console.log(document.getElementById('employee')[0].value)
First off, I don't think you need to access the 0th element when calling getElementById.
console.log(document.getElementById('employee').value).
Then, you are using vue! Make use of v-model.
The code below is not tested, use it as a guide only.
<template>
<select id="employee" v-model="selectedEmployee">
<option v-bind:key="employee" v-for="employee of employees" v-bind:value="{id: employee.id}">{{ employee.name }}</option>
</select>
</template>
<script>
export default {
data: {
selectedEmployee: null
},
watch: {
selectedEmployee: (newVal) => { console.log(newVal) }
}
}
</script>
If you are not aware of v-model, you don't know Vue.
Read about Form Input Bindings in Vue
In my Vue JS project I'm trying to create a generic component to render a bunch of checkboxes onto the page. I need to send the value back to the component to attach a v-model on the component.
Thus far, my checkboxes all allow me to choose true/false, but I only want to send back one true value, meaning if I select 2 out of 4 checkboxes, the v-model on my custom component should have the value of true
I've rendered my checkboxes, but am struggling to get the v-model to work, where am I going wrong?
<GroupedCheckboxes :options="editor.sources" v-model="source.isChecked" />
And the component is:
<template>
<div>
<div v-for="(checkbox, index) in options" :key="index">
<input type="checkbox">
</div>
</div>
</template>
<script>
export default {
props: ['options']
}
</script>
My v-model needs to retreive the value from the group, but isn't
Issue 1: GroupedCheckboxes doesn't implement v-model
For v-model on a component to work, the component must:
Receive a value prop 1️⃣
Emit an input event with a new value 2️⃣ Since you want the value to be true only if any of the checkboxes are checked, use Array.prototype.some()
Issue 2: GroupedCheckboxes doesn't implement checkbox groups
Checkbox groups must:
Have an initial value of type Array 3️⃣
Have the same name 4️⃣
<template>
<div>
<div v-for="(checkbox, index) in options" :key="index">
<label>
<input
type="checkbox"
name="myCheckboxGroup" 4️⃣
:value="checkbox"
v-model="myValue"
#change="$emit('input', myValue.some(v => v))" 2️⃣
>
{{ checkbox }}
</label>
</div>
</div>
</template>
<script>
export default {
props: [
'options',
'value', 1️⃣
],
data() {
return {
myValue: [], 3️⃣
}
},
}
</script>
demo
I am using angular I want to send information about what element I selected in the select option. Specifically I want to send the data-value to a variable in my product-form.component.ts.
I tried using ngModel but I keep getting errors saying that it doesn't recognize (click)=selectCategory1(category) use a function I am using template forms for my forms that could be the reason. You can see my code live at :
https://stackblitz.com/github/RashellSmith/Dashboard-FrontEnd
Product Form component html
<div class="form-group">
<label for="productCategory">Product Category</label>
<select [(ngModel)]="model.category" (click)=selectCategory1(category) name="category" class="form-control" id="productCategory" (click)="value()">
<option *ngFor="let category of categories" (click)=selectCategory1(category) data-value="{{category.category}}" id={{category.categoryName}} >{{category.categoryName}}</option>
</select>
</div>
Product Form component ts
export class ProductFormComponent implements OnInit {
suppliers: Supplier[];
categories: Category[];
value: number;
model = new Newproduct("name",new Category( this.value,"name"),66,33,true,new Supplier(null,null),76);
selectCategory1(Category){
console.log(Category);
}
submitted = false;
get diagnostic() { return JSON.stringify(this.model); }
onSubmit() { this.submitted = true; }
constructor(private supplierService: SupplierService,private categoryService: CategoryService) { }
ngOnInit() {
this.supplierService.getAll().subscribe(data => {
this.suppliers = data;
});
this.categoryService.getAll().subscribe(data => {
this.categories = data;
});
}
}
You are definitely trying to over-complicate things. You need to bind a simple (change) method to your select list which would be be triggered on value change. You can either pass the value to this function using template reference variable as
<Select #select (change)="SelectChanged(select.value)">
Or you can bind [(ngModel)] directive and access that directly in your component class on (change)
A sample template code:
<select [(ngModel)]="selectedOption" (change)="GetSelectedValue(selectedOption)">
<option *ngFor="let option of options" [ngValue]="option">{{option}}</option>
</select>
And component class would look like
GetSelectedValue(val) {
//do something with val
}
Stackblitz at: https://stackblitz.com/edit/angular-r4d7ul
I have a parent component and multiple child components, which use the same prop. This prop is an array of keys for a dropdown menu in element.js.
When the children render the first time, they contain no data. However, once the keys from arrive using vuefire the children get the dropdown menu items. However, the element dropdown menu is not rerendered as it should have been.
However using the vue dev tools, I can see that the dropdown menu entries have been passed down as a key. When vue does a hot reload, because of a file change, the keys will load.
Once the entries are loaded, I can select the entry and everything works as expected.
I also had the same results using the vuetify dropdown and the HTML dropdown. Both have the same issue.
parent
<template>
<div class="setup">
<h1>Setup</h1>
<div class="selectIngredients" v-for="number in 6">
<setupSelection :bottle="number" :ingredients="options" />
</div>
</div>
</template>
<script>
import {db} from "#/firebaseConfig"
import setupSelection from '#/components/setupSelection';
export default {
components: {
setupSelection,
},
firestore: {
options: db.collection('ingredients'),
},
};
</script>
child
<template>
<div class="ingredientSelector">
<h3>Select for Pump <span>{{bottle}}</span></h3>
<el-select v-model="selected" clearable placeholder="Select" >
<el-option
v-for="ingredient in ingredients"
v-bind:key="ingredient.text"
v-bind:label="ingredient.text"
v-bind:value="ingredient">
</el-option>
</el-select>
<!-- <v-select
v-model="selected"
:items="ingredients"
label="Select a favorite activity or create a new one"
></v-select> -->
<!-- <select v-model="selected" v-for="ingredient in ingredients">
<option :value="ingredient.value">{{ingredient.text}}</option>
</select> -->
</div>
</template>
<script>
import {db} from "#/firebaseConfig";
export default {
props: {
ingredients: { required: true },
bottle: { type: Number, required: true },
},
data() {
return {
selected: ''
}
},
},
};
</script>
I expected the dropdown menu to update once the client received them.
Thank you!
I haven't used Vuefire myself but I read the following in the documentation:
Make sure to create any property added to firestore in data as well
https://github.com/vuejs/vuefire/tree/master/packages/vuefire#firestore-option
Similar advice is given here:
https://vuefire.vuejs.org/vuefire/binding-subscriptions.html#declarative-binding
In your example you don't have options in the parent's data. This would, presumably, leave it non-reactive, leading to the symptoms you describe.
Use a data property for your items, and set them after the options are loaded.
data() {
return {
options: []
}
},
created() {
db.collection('ingredients').then(data=> this.options = data}
}
The promise returned from db.collection('ingredients') is not reactive.
Even better approach would be to set options: null, and show a loading indicator until it is an array.