Populate text value when option selected from dropdown in vue - vue.js

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>

Related

Vuejs changing the selected target index to data from an array that pertains to the selection

I am trying something different using vuejs, but i want to instead replace it with the nested array data instead of the selected text. As you can see, the selectedChar is displaying the name. Displaying the name there is fine, but in reality i would like to get the value of subjects there if that name is selected in the dropdown
new Vue({
el: "#app",
data: {
todos: [
{ Name: 'DonaldDuck',dept:'Disney',subjects: ["DA_", "AS_1E1", "VV_123", "AP_DS1"] },
{ Name: "Sonic", dept:'Marvel',subjects: ["SA_", "KSL_111", "DD_123", "GP_1SA1"]},
],
selectedChar:'null'
},
methods: {
changeZ (event) {
this.selectedChar = event.target.options[event.target.options.selectedIndex].text
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Todos:</h2>
<select id="dept"class="form-control" #change="changeZ($event)" style="width: 210px;">
<option value="Select a Department" selected disabled>select a character:</option>
<option v-for="t in todos" :value="todos.id" :key="todos.id">{{ t.Name }}</option>
</select>
<p><span>You Selected: {{ selectedChar }}</span></p>
instead. thanks!
You should really be utilizing v-model to bind the value of the <select> to a data property. The Vue docs specifically have a subsection on binding objects to <select> inputs. When you bind the whole object value you can then display any property of that object.
<select
id="dept"
class="form-control"
v-model="selection"
style="width: 210px"
>
<option v-for="t in todos" :value="t" :key="t.id">
{{ t.Name }}
</option>
</select>
<p>
<span>You Selected: {{ selection.subjects }}</span>
</p>
data: {
todos: [
{ Name: 'DonaldDuck',dept:'Disney',subjects: ["DA_", "AS_1E1", "VV_123", "AP_DS1"] },
{ Name: "Sonic", dept:'Marvel',subjects: ["SA_", "KSL_111", "DD_123", "GP_1SA1"]},
],
selection: {}
}

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
};
},

how to show a suggestion list on inputting a tag in bootstrap vue?

I am working on a Vue Assignment. For styling I am using BootstapVue. What I need to achieve is, whenever an user is entering a text in the input field, a filtered array that is containing the value should be displayed as dropdown. The User can either press Enter key stroke or select the value from dropdown. If the input text is not in the array, then the user should not be able to enter the value i.e. the tag might not be created. For example, whenever we are creating a question here in Stack Overflow, upon typing tags and entering or selecting from the suggestion cards. I I need similar functionality except that if the input text is not in the suggestion array, the user will not be able to create the tag
Here's what I have tried so far :
App.vue
<template>
<div>
<b-form-tags
v-model="value"
#input="resetInputValue()"
tag-variant="success"
:state="state"
>
<template v-slot="{ tags, inputId, placeholder, addTag, removeTag }">
<b-input-group>
<b-form-input
v-model="newTag"
list="my-list-id"
:id="inputId"
:placeholder="placeholder"
:formatter="formatter"
#keypress.enter="addTag(newTag)"
></b-form-input>
</b-input-group>
<b-form-invalid-feedback :state="state">
Duplicate tag value cannot be added again!
</b-form-invalid-feedback>
<ul v-if="tags.length > 0" class="mb-0">
<li
v-for="tag in tags"
:key="tag"
:title="`Tag: ${tag}`"
class="mt-2"
>
<span class="d-flex align-items-center">
<span class="mr-2">{{ tag }}</span>
<b-button
size="sm"
variant="outline-danger"
#click="removeTag(tag)"
>
remove tag
</b-button>
</span>
</li>
</ul>
<b-form-text v-else>
There are no tags specified. Add a new tag above.
</b-form-text>
</template>
</b-form-tags>
<datalist id="my-list-id">
<option>Manual Option</option>
<option v-for="(size, ind) in sizes" :key="ind">{{ size }}</option>
</datalist>
</div>
</template>
<script>
export default {
data() {
return {
newTag: "",
value: [],
sizes: ["Small", "Medium", "Large", "Extra Large"],
};
},
computed: {
state() {
// Return false (invalid) if new tag is a duplicate
return this.value.indexOf(this.newTag.trim()) > -1 ? false : null;
},
},
methods: {
resetInputValue() {
this.newTag = "";
},
formatter(value) {
if (this.sizes.includes(value)) {
return value.toUpperCase();
}
return ;
},
},
};
</script>
How can I achieve the same? Thanks
You can achieve it by using b-form-datalist with the use of <b-form-input> by adding a change event to reset the input value if there is no data in the dataList as per the search value.
Demo :
new Vue({
el: '#app',
data: {
sizes: ['Small', 'Medium', 'Large', 'Extra Large'],
tag: null
},
methods: {
checkTag(e) {
const tagsAvailable = this.sizes.filter((item) => item === e);
if (!tagsAvailable.length) {
this.tag = ''
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/2.21.2/bootstrap-vue.min.css"/>
<div id="app">
<b-form-input list="my-list-id" v-model="tag" v-on:change="checkTag($event)"></b-form-input>
<datalist id="my-list-id">
<option>Manual Option</option>
<option v-for="size in sizes">{{ size }}</option>
</datalist>
</div>

Array of Dynamic Dependent Select Box in Vue.js

I have an array of Depend select box which contains Universities and courses. Each university has its own course. and I have built the course dropdown which depends on the university. I can successfully get university course from server request but the problem is when I change the select university it's changing all course fields. How I can get rid of the problem please give me some ideas. Thanks
<template>
<form #submit.prevent="handleSubmit">
<div class="col col-md-12">
<div v-for="(interest, index) in interests" :key="index" class="row">
<div class="col col-md-6">
<div class="form-group mb-4">
<label for="select-ins">Interested Universities </label>
<select
v-model="interest.institute_id"
class="form-control"
#change="onChangeUniversity($event)"
>
<option disabled value="">Select a University</option>
<option
v-for="institute in institutes"
:key="institute.id"
:value="institute.id"
>
{{ institute.institute_name }}
</option>
</select>
</div>
</div>
<div class="col col-md-6">
<div class="form-group mb-4">
<label>Interested Course</label>
<select
v-model="interest.course_id"
class="form-control"
#change="onChangeCourse($event)"
>
<option disabled value="">Select a Course</option>
<option
v-for="course in courses"
:key="course.id"
:value="course.id"
>
{{ course.course_name }}
</option>
</select>
</div>
</div>
<div class="col col-md-12 text-right">
<div class="row ml-4">
<div v-show="index == interests.length - 1">
<button
class="btn btn-warning mb-2 mr-2 btn-rounded"
#click.prevent="add"
>
Add
</button>
</div>
<div v-show="index || (!index && interests.length > 1)">
<button
class="btn btn-danger mb-2 mr-2 btn-rounded"
#click.prevent="remove"
>
Remove
</button>
</div>
</div>
</div>
</div>
</div>
</form>
</template>
<script>
export default {
data() {
return {
institutes: [],
courses: [],
interests: [
{
institute_id: "",
course_id: "",
},
],
};
},
mounted() {
axios.get("/institues").then((res) => {
this.institutes = res.data;
});
},
methods: {
onChangeUniversity(event) {
let universityId = event.target.value;
axios.get(`/institute-course/${universityId}`).then((res) => {
this.courses = res.data;
});
},
add() {
this.interests.push({
institute_id: "",
course_id: "",
});
},
remove(index) {
this.interests.splice(index, 1);
},
},
};
</script>
check screenshot
http://prntscr.com/115mkn5
lets start at your mounted hook.
you call for a API to receive all available institutes. so far so good. each institute got is own ID, this is important for later, lets keep that in mind.
now your using a function which will then call on a "change" event, like onChangeUniversity this is a good way on preventing to overload data in a page, nice idea just to fetch data only when they are needed.
then comes the tricky part which makes it difficult for you and everyone else reading your code.
you have this courses array in your data which normally belongs to the related institute. this array should not be handled as a second array apart from institutes, it should be a child of it.
like check this data structur:
institutes: [
{
institute_name: "WhatEver1",
id: 0,
courses: [{ course: 1 }, { course: 2 }, { course: 3 }],
}
]
instead of this:
institutes: [
{
institute_name: "WhatEver1",
id: 0,
},
],
courses: [{ course: 1 }, { course: 2 }, { course: 3 }],
the first option above is a good nested way to display your data as loop inside a loop.
which means you have to push your courses inside your institute of choice with the belonged id.
your onChangeUniversity function should than do something like this:
onChangeUniversity(event) {
let universityId = event.target.value;
axios.get(`/institute-course/${universityId}`).then((res) => {
const foundedId = this.institutes.findIndex(institute => institute.id === universityId)
this.institutes[foundedId].courses = res.data
});
},
after that its much easier to iterate over and display the data inside the options. and i am sure you will not have that issue anymore.
just try it like that first and give feedback.
Update
<div class="form-group mb-4">
<label>Interested Course</label>
<select
v-model="interest.course_id"
v-if="institute.courses.length !== 0" <-------HERE
class="form-control"
#change="onChangeCourse($event)"
>
<option disabled value="">Select a Course</option>
<option
v-for="course in institute.courses"
:key="course.id"
:value="course.id"
>
{{ course.course_name }}
</option>
</select>
</div>
you need a render condition to stop your courses loop from being iterated when there is no data inside.
also make sure you await till the fetching of courses is completed.
async onChangeUniversity(event) {
let universityId = event.target.value;
await axios.get(`/institute-course/${universityId}`).then((res) => {
this.courses = res.data;
});
},
and also in your mounted hook
async mounted() {
await axios.get("/institues").then((res) => {
this.institutes = res.data;
});
},
if you still struggle please give me a CodeSandbox of your current code.

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.