Creating input tag as v-model's length - vue.js

<div v-if="quesType === 'Çoktan Seçmeli'" class="row p-3 bg-dark text-light">
<div class="col-4">
<select v-model="coktanSecmeli" class="form-select" name="" id="">
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</div>
<div v-for="item in coktanSecmeli">
<input type="text"/>
</div>
</div>
export default defineComponent({
name: "SoruEkle",
data() {
const quesType = "";
const coktanSecmeli = 0;
return {
quesType,
coktanSecmeli,
};
},
components: {
ErrorMessage,
Field,
Form,
},
props: {
widgetClasses: String,
},
methods: {},
});
i tried but i cant fix this. how can i get v-model's length and use this length to create html tag as this model's length. i also tried with array and v-html but it didn't worked.

You have to create a range for coktanSecmeli. Let's define computed property for this:
range() {
return [...Array(this.coktanSecmeli).keys()];
}
or with a standard syntax:
range() {
return Array.from(Array(this.coktanSecmeli).keys());
}
Then you should use this range for v-for:
<div v-for="key in range">
<input type="text" :key="key"/>
</div>

Related

Vue v-model/v-for doesn't update on mount, but after first manual change

I have a dropdown list "functions" that is filled with database entries and a dropdown list with 2 hardcoded entries. When the vue website is opened the dropdown list remains empty but as soon as I change the value of the other dropdown field the desired data from the database is available.
I'm a bit confused because I expected that adding "retrieveFunctions()" into the mounted() function would trigger the v-for, and even more confused that changing something in another select field suddenly triggers it.
The HTML code:
<template>
<div class="submit-form">
<div v-if="!submitted">
<div class="row">
<div class="col-sm-12">
<p><a style="width:500px" class="btn btn-info" data-toggle="collapse" href="#generalInformation" role="button" aria-expanded="true" >
General Information</a></p>
<div class="collaps show" id="generalInformation">
<!-- NAME -->
<div class="form-group">
<input placeholder="Name" type="text" class="form-control"
id="name" required v-model="component.name" name="name">
</div>
<!-- DOMAIN -->
<div class="input-group mb-3">
<div class="input-group-prepend">
<label style="width:100px" class="input-group-text" for="inputGroupDomain">Domain</label>
</div>
<select v-model="component.domain"
class="custom-select"
id="inputGroupDomain"
>
<option value="Power System">Power System</option>
<option value="ICT">ICT</option>
</select>
</div>
<!-- FUNCTION -->
<div class="input-group mb-3">
<div class="input-group-prepend">
<label style="width:100px" class="input-group-text" for="inputGroupFunction">Functions</label>
</div>
<select v-model="_function" class="custom-select" id="inputGroupFunction">
<option :class="{ active: index == currentIndex }"
v-for="(_function, index) in functions"
:key="index"
value= _function.name>
{{ _function.name }}
</option>
</select>
</div>
</div>
<p>
<button #click="saveComponent" class="btn btn-success">Add Component</button>
</p>
</div>
</div>
</div>
<div v-else>
<h4>Component was added succesfully!</h4>
<button class="btn btn-success" #click="newComponent">Proceed</button>
</div>
The script part:
<script>
import FunctionDataService from "../services/FunctionDataService";
export default {
name: "add-component",
data() {
return {
component: {
id: null,
name: "",
type: "",
domain: "",
},
submitted: false
};
},
methods: {
retrieveFunctions() {
FunctionDataService.getAll()
.then(response => {
this.functions = response.data;
console.log(response.data);
})
.catch(e => {
console.log(e);
});
},
refreshList() {
this.retrieveFunctions();
},
},
mounted() {
this.retrieveFunctions();
}
};
</script>
refreshList() {
this.retrieveFunctions();
},
},
mounted() {
this.retrieveFunctions();
}
};
</script>
State in the beginning: Dropdown list empty
State after selecting something in the upper dropdown list: Database entries are visible and correct
You need to initiate all responsive properties on the data return object with either a value (empty string, array, object, etc) or null. Currently it's missing the _function attribute used in the select v-model and the functions array used in the v-for. You can try to change the data to the following:
data() {
return {
_function: "",
functions: [],
component: {
id: null,
name: "",
type: "",
domain: "",
},
submitted: false
};
},

Populate text value when option selected from dropdown in vue

I have a vue app and new to vue. I have a dropdown which is populated via an axios endpoint. This returns 2 items. What I'm trying to do is, if 'APC' is selected, then populate a text value with an attribute value returned in my array but this is where I may be overthinking.
My thinking is that I need to iterate over the items again but if a condition is met display the value.
Below is my whole page code
<template>
<div>
<div class="form-group row">
<label class="col-sm-3 col-form-label" for="courierList">Courier <span class="text-danger">*</span></label>
<div class="col-sm-7 shipping-options">
<select id="courierList" class="form-control" v-model="selectedCourier">
<option value='courierDefault'>Please select a courier</option>
<option :value="courier.name.toLowerCase()" v-for="(courier, index) in couriers" :key="courier.index">
{{ courier.name }}
</option>
</select>
</div>
</div>
<span v-if="selectedCourier != 'courierDefault'">
<div class="form-group row">
<b class="col-sm-3" for="cutOff">Order cut-off</b>
<div class="col-sm-7 shipping-options" v-for="(cutOff, index) in couriers" :key="cutOff.index">
{{ cutOff.cut_off }}
</div>
</div>
</span>
</div>
</template>
<script>
export default {
name: 'CourierSelect',
data() {
return {
couriers: [],
selectedCourier: 'courierDefault'
}
},
mounted() {
this.fetchCouriers();
},
methods: {
fetchCouriers() {
axios.get('/CHANGED_FOR_SECURITY')
.then((response) => {
this.couriers = response.data.couriers;
console.log('axios_couriers', this.couriers)
})
.catch((error) => {
VueEvent.$emit('show-error-modal', 'cartFethchCouriers');
console.log(error);
});
}
}
}
</script>
My console.log for 'axios_couriers' gives
Then when I select 'APC' my page displays as
But what I need is for the 'cut_off' value (displayed in the console screenshot) for the 'APC' Array object to display only. The value should be 16:30
Is there a way to do this as a Computed prop or something?
As you suggested a computed should indeed work.
One way would be:
currentCutOff() {
return this.couriers.find(c => c.name == this.selectedCourier).cut_off;
}
This tries to find the courier from your array which equals the name of the currently selectedCourier.
There is a much simplier solution with vuejs data binding.
Check this code:
const vm = new Vue({
el: '#app',
data() {
return {
items: [{
id: 1,
name: 'AAA',
time: '14:00'
},
{
id: 2,
name: 'BBB',
time: '18:00'
}
],
selected: null
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<select v-model="selected">
<option disabled value="null">Please select one</option>
<option v-for="item in items" v-bind:value="item">
{{ item.name }}
</option>
</select>
<div>Selected: {{ selected? selected.time : 'nothing selected' }}</div>
</div>

Select is not populating via API call in VueJS

I'm very new to Vue and I'm doing Vue just because I need to use it in a project. Right now, I'm trying to populate a 'Select' by performing an API call. However, it is not working. Here's the code.
<template>
<form method="POST">
<label>
Website Name
</label>
<select name="website_id">
<option v-for="item in this.websiteData" :value="item.id">{{item.domain}}</option>
</select>
<input type="submit"/>
</form>
</template>
<script>
export default {
async beforeMount() {
await fetch('/api/get-website').then(res=>res.json()).then(data=>this.websiteData = data.map(item=>item));
console.log(this.websiteData);
},
// name: "FormComponent"
data(){
return {
websiteData: [],
postData: null
}
},
methods: {
}
}
</script>
<select name="website_id">
<option v-for="item in websiteData" :value="item.id">
{{item.domain}}
</option>
</select>
Typo in websiteData. In template you can access variables without this.

How to get object relation inside <option> loop in Vue?

Let's go to the point of the question. I have a selection component, it looks like this:
<template>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="category_id">
Product Category
</label>
<select
name="category_id"
id="category_id"
:class="form.errors.has('category_id') ? 'form-control is-invalid' : 'form-control'"
v-model="form.sharedState.category_id">
<option value="" disabled hidden>Select Category</option>
<option
v-for="category in categories"
:key="category.id"
v-text="category.name"
:value="category.id"
#click="$emit('category-selected', category.sub_categories)">
</option>
</select>
<small
class="form-text text-danger"
v-if="form.errors.has('category_id')"
v-text="form.errors.get('category_id')"></small>
</div>
</div>
<div class="col-md-6">
<div
class="form-group"
v-if="revealSubCategory"
#category-selected="show">
<label for="category_id">
Sub Category
</label>
<select
name="sub_category_id"
id="sub_category_id"
:class="form.errors.has('sub_category_id') ? 'form-control is-invalid' : 'form-control'"
v-model="form.sharedState.sub_category_id">
<option value="" disabled hidden>Select Sub Category</option>
<option
v-for="subcategory in subcategories"
:key="subcategory.id"
v-text="subcategory.name"
:value="subcategory.id">
</option>
</select>
<small
class="form-text text-danger"
v-if="form.errors.has('category_id')"
v-text="form.errors.get('category_id')"></small>
</div>
</div>
</div>
</template>
<script>
import BaseCard from './BaseCard.vue';
export default {
components: {
BaseCard
},
data() {
return {
categories: [],
revealSubCategory: false,
subcategories: [],
form: new Form({
sharedState: product.data
})
}
},
mounted() {
this.getCategories();
},
methods: {
getCategories() {
axios.get('categories')
.then(({data}) => this.categories = data);
},
show(subcategories) {
this.revealSubCategory = true;
this.subcategories = subcategories
}
}
}
</script>
And a select sub category input (it is there on the second column) which is will be displayed once the user has selected one of the categories options. Each category option has relation to sub categories taken from the API.
How can I get the sub categories and display the input? I already tried #change on the select tag but I can't pass the sub category object because it is outside the loop. And #click event seems to be not working in an option tag.
You can watch the v-model of the first select and change subcategory.
watch:{
"form.sharedState.category_id": function (val) {
// update subcategories here
}

Set same value for sibling component's select

Using v-for, I am looping through a component. The component is for each client. In this component, I have same form for each client and when a select value is selected for the first component (client 1), I want to select this value for every client.
Do I need to pass the data to the root and create a single source of truth variable?
I tried setting up a basic version:
<div id="app">
<my-comp v-for="x in 2" v-bind:val="x"></my-comp>
</div>
Vue.component('my-comp', {
props: ['val'],
template: `
<div>
<div>
<label>Status</label>
<select :data-client="val" #change="statusChanged">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
methods: {
statusChanged(e) {
var client = e.target.getAttribute('data-client')
if (client == 1) {
alert('set same value for client 2')
}
}
}
})
new Vue({
el: '#app',
})
Here is a fiddle: https://jsfiddle.net/w53164t2/
I considered a little bit after my original answer and have come up with something I think is a little bit more real world than the example fiddle provided in the original question; specifically it is easy to make all the selects reflect the same value if they are all using the same source value, however I expect in a real world scenario each component would be independently bound to a single client. Each client would want their individual value to change, with the one caveat that if a "master" client changed, then all non-master clients should change to the master client's value.
To that end, this might be a case where I think a component specific bus is appropriate. The master would emit an event when it's value changed and the the other clients would set their value with respect to the master.
console.clear()
const MyCompBus = new Vue()
Vue.component('my-comp', {
props: ['val', 'master'],
computed:{
selected:{
get(){return this.val},
set(v){
this.$emit('update:val', v)
if (this.master)
MyCompBus.$emit("master-updated", v)
}
}
},
methods:{
onMasterUpdated(newMasterValue){
if (this.master) return
this.selected = newMasterValue
}
},
created(){
MyCompBus.$on('master-updated', this.onMasterUpdated)
},
beforeDestroy(){
MyCompBus.$off('master-updated', this.onMasterUpdated)
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
new Vue({
el: '#app',
data:{
masterValue: null,
clients:[
{id: 1, selectedValue: null, master: true},
{id: 2, selectedValue: null},
{id: 3, selectedValue: null},
{id: 4, selectedValue: null},
]
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<my-comp v-for="client in clients"
:val.sync="client.selectedValue"
:master="client.master"
:key="client.id">
</my-comp>
{{clients}}
</div>
Original Answer
Bind them all to the same value using v-model.
Vue.component('my-comp', {
props: ['value'],
computed:{
selected:{
get(){return this.value},
set(v){this.$emit('input', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 2" v-model="selectedValue" :key="x"></my-comp>
Here is the updated fiddle.
If you want to stick with val as the property you can use .sync instead.
Vue.component('my-comp', {
props: ['val'],
computed:{
selected:{
get(){return this.val},
set(v){this.$emit('update:val', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 2" :val.sync="selectedValue" :key="x"></my-comp>
Example fiddle.
If you want just one of them designated as a "master" select, then add a property that does so.
Vue.component('my-comp', {
props: ['val', 'master'],
computed:{
selected:{
get(){return this.val},
set(v){if (this.master) this.$emit('update:val', v)}
}
},
template: `
<div>
<div>
<label>Status</label>
<select v-model="selected">
<option selected="" disabled="" value="0"></option>
<option value="xxx">Xxx</option>
<option value="yyy">Yyy</option>
<option value="zzz">Zzz</option>
</select>
</div>
</div>
`,
})
And in the template:
<my-comp v-for="x in 5" :val.sync="selectedValue" :master="1 == x" :key="x"></my-comp>
Example fiddle.