How do I get values from multiple vue select instances? - javascript

I have 2 arrays of objects like below. I need to create a select for every questions element, and I need to have the options connected with the selections by the id. In this case I need to have 2 selects, the first one will have 1000, 5000 and 10000 as options meanwhile the second select will have yes and no as options
const questions = [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
]
const selections = [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
]
I made it like this in vue but the issue is that I can't save the data in the v-model as I'm returning the values from cars() and not a variable specified in data()
<div class="form-group" v-for="item in questions" v-bind:key="item.question">
<h5 v-if="showQuestion(item.id)">{{ item.question }}</h5>
<div class="tour-options-select" v-if="showQuestion(item.id)">
<select id="select-suggestions" name="tour-options-dropdown" class="tour-options-dropdown" v-model="questions.value">
<option v-for="item1 in cars(item.id)" v-bind:key="item1.id" :value="item1.display_name">{{ item1.display_name }}</option>
</select>
</div>
</div>
Ultimately, I just need to know, how do I get the value when I have a structure like the one I defined above?

If you have an array in data() to store your selected options, you can use v-model to dynamically bind with an element in that array if you give it an index:
new Vue({
el: '#app',
data: {
questions: [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
],
selections: [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
],
selected: [],
},
methods: {
cars: function(id) {
return this.selections.reduce((arr, currSel) => {
if (currSel.question_id == id)
arr.push(currSel);
return arr;
}, []);
},
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<div id="app">
<div v-for="(question, index) in questions" :name="question.question" :key="question.id">
<select v-model="selected[index]">
<option v-for="option in cars(question.id)" :key="option.question_id" :value="option.value">
{{ option.value }}
</option>
</select>
</div>
<p>Selected:</p>
<pre>{{ $data.selected }}</pre>
</div>
Another approach would be to use events to handle the changes, calling a custom function each time the user makes a selection, eg using #change:
new Vue({
el: '#app',
data: {
questions: [{
'id': 1,
'question': 'KM'
},
{
'id': 2,
'question': 'Works'
}
],
selections: [{
'question_id': 1,
'value': 1000
},
{
'question_id': 1,
'value': 5000
},
{
'question_id': 1,
'value': 10000
},
{
'question_id': 2,
'value': 'yes'
},
{
'question_id': 2,
'value': 'no'
}
],
},
methods: {
cars: function(id) {
return this.selections.reduce((arr, currSel) => {
if (currSel.question_id == id)
arr.push(currSel);
return arr;
}, []);
},
}
});
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<div id="app">
<div v-for="(question, index) in questions" :name="question.question" :key="question.id">
<select #change="console.log('Option', $event.target.value, 'chosen for Q', question.id)">
<option selected disabled>Select...</option>
<option v-for="option in cars(question.id)" :key="option.question_id" :value="option.value">
{{ option.value }}
</option>
</select>
</div>
</div>
This way will give you more freedom to store or process the data as you wish, but you'll have to do it manually.

Related

Input field loos focus in Vue when has no results

I have Vue application. And inside, I have some input field. If this field has any results, the buttons forward and backward are visible, else not.
My problem is, that when I type inside input field, when I type something that has no results, input loose focus. (see snippet)
Hot to solve this?
new Vue({
el: '#app',
data: {
input: "",
items: [{
'id': 123,
'name': 'item1'
},
{
'id': 124,
'name': 'item2'
},
{
'id': 128,
'name': 'item3'
},
{
'id': 237,
'name': 'item4'
}
]
},
computed: {
search_list_of_workorders: function() {
var self = this;
var search_string = this.input.toLowerCase();
// apply filter
var array = this.search_filter_array(this.items, search_string);
return array.slice(0, 10).map(a => a.id);
},
number_of_search_results: function() {
return this.search_list_of_workorders.length
},
display_results_buttons: function() {
return this.number_of_search_results > 0
},
},
methods: {
search_filter_array: function(array, search_string) {
return array.filter(function(el) {
var id_filter = el.id.toString().includes(search_string);
return id_filter;
});
},
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<button type="button" v-if="display_results_buttons">
Back
</button>
<div v-if="display_results_buttons">({{ number_of_search_results }})</div>
<input placeholder="Search" type="text" list="searchDropDown" id="searchInput" name="selectEventInput" v-model="input" />
<datalist id="searchDropDown">
<option v-for="(item, index) in search_list_of_workorders" :value="item" :key="`optionEvents_${index}`" >
</option>
</datalist>
<button type="button" v-if="display_results_buttons">
Forward
</button>
</div>
Use v-show instead of v-if. This will fix your issue perfectly!
The main difference:
v-if: Only renders the element to the DOM if the expression passes.
v-show: Renders all elements to the DOM and then uses the CSS display property to hide elements if the expression fails.
Use cases:
v-show: expensive initial load, cheap toggling,
v-if: cheap initial load, expensive toggling.
In your case, toggling is mandatory and it can be required many times, so v-show is a better solution. Also, it doesn't require re-rendering and will fix the focus-losing issue as well.
new Vue({
el: '#app',
data: {
input: "",
items: [{
'id': 123,
'name': 'item1'
},
{
'id': 124,
'name': 'item2'
},
{
'id': 128,
'name': 'item3'
},
{
'id': 237,
'name': 'item4'
}
]
},
computed: {
search_list_of_workorders: function() {
var self = this;
var search_string = this.input.toLowerCase();
// apply filter
var array = this.search_filter_array(this.items, search_string);
return array.slice(0, 10).map(a => a.id);
},
number_of_search_results: function() {
return this.search_list_of_workorders.length
},
display_results_buttons: function() {
return this.number_of_search_results > 0
},
},
methods: {
search_filter_array: function(array, search_string) {
return array.filter(function(el) {
var id_filter = el.id.toString().includes(search_string);
return id_filter;
});
},
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<div id="app">
<button type="button" v-show="display_results_buttons">
Back
</button>
<div v-show="display_results_buttons">({{ number_of_search_results }})</div>
<input placeholder="Search" type="text" list="searchDropDown" id="searchInput" name="selectEventInput" v-model="input" />
<datalist id="searchDropDown">
<option v-for="(item, index) in search_list_of_workorders" :value="item" :key="`optionEvents_${index}`" >
</option>
</datalist>
<button type="button" v-show="display_results_buttons">
Forward
</button>
</div>

In Vue.js, how can I get an event to fire in a select when the displayed element is chosen?

Here is an example fiddle:
https://jsfiddle.net/40fxcuqd/
Initially, it displays "Carl"
If I select Carol, Clara etc, then an event will fire and data will print to the console.
But if I click the dropdown and choose "Carl", no event will fire, and nothing will print to the console.
The event I'm using is #input:
<select v-model="selectedPerson" #input="myEvent()">
How can I get an event to fire every time something is selected, even if it's the same value?
Edit:
To clarify, when "Carl" is initially selected:
and then the dropdown is opened:
and then Carl is selected again, I would like an event to be triggered and a print to the console. My issue at the moment is no event is triggered, and nothing prints to the console.
That is because the selected option by default is 1, then nothing change when you click on Carl, you must use #change event and if you want to get Carl value when you do click should use placeholder on select option.
new Vue({
el: '#app',
template: `
<div>
<select v-model="selectedPerson" #change="myEvent()">
<option :value="null" disabled hidden>Select option</option>
<option v-for="person in people" :value="person.key" :selected="person.key == selectedPerson">{{person.name}}</option>
</select>
</div>
`,
data: {
people: [
{key: 1, name: "Carl"},
{key: 2, name: "Carol"},
{key: 3, name: "Clara"},
{key: 4, name: "John"},
{key: 5, name: "Jacob"},
{key: 6, name: "Mark"},
{key: 7, name: "Steve"}
],
selectedPerson: null
},
methods: {
myEvent: function() {
console.log(this.selectedPerson);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
Really hacky but does the job, I've used #click and event.detail or event.which:
new Vue({
el: '#app',
template: `
<div>
<select v-model="selectedPerson" #input="myEvent($event)" #click="myEvent($event)">
<option v-for="person in people" :value="person.key" :selected="person.key == selectedPerson">{{person.name}}</option>
</select>
</div>
`,
data: {
people: [{
key: 1,
name: "Carl"
},
{
key: 2,
name: "Carol"
},
{
key: 3,
name: "Clara"
},
{
key: 4,
name: "John"
},
{
key: 5,
name: "Jacob"
},
{
key: 6,
name: "Mark"
},
{
key: 7,
name: "Steve"
}
],
selectedPerson: 1
},
methods: {
myEvent: function(e) {
if (e.detail == 0)//if (e.which == 0)
console.log(e.type, this.selectedPerson);
}
}
});
body {
margin: 20px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.as-console-wrapper {
height: 39px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app"></div>
A less hacky way using data:
new Vue({
el: '#app',
template: `
<div>
<select v-model="selectedPerson" #input="myEvent($event)" #click="myEvent($event)">
<option v-for="person in people" :value="person.key" :selected="person.key == selectedPerson">{{person.name}}</option>
</select>
</div>
`,
data: {
people: [{
key: 1,
name: "Carl"
},
{
key: 2,
name: "Carol"
},
{
key: 3,
name: "Clara"
},
{
key: 4,
name: "John"
},
{
key: 5,
name: "Jacob"
},
{
key: 6,
name: "Mark"
},
{
key: 7,
name: "Steve"
}
],
selectedPerson: 1,
prev: 0,
isChanged: false
},
methods: {
myEvent: function(e) {
if (e.type == "input" || (e.type == "click" && !this.isChanged && (this.prev == this.selectedPerson || this.prev == 0))) {
this.isChanged = true;
this.prev = 0;
} else if (e.type == "click" && this.isChanged) {
console.log(e.type, this.selectedPerson);
this.prev = this.selectedPerson;
this.isChanged = false;
}
}
}
});
body {
margin: 20px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.as-console-wrapper {
height: 39px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app"></div>
when you change the dropdown you will get the index of people array and you can do something like this to get value
myEvent: function() {
console.log(this.people[this.selectedPerson].name);
}
}
one workaround is to set selected to a not used value when focus, then change event will fire no matter which option is selected.
<select
v-model="selectedPerson"
ref="s"
#focus="selectedPerson = 0"
#change="myEvent()"
>
see fiddle: https://jsfiddle.net/tne1wp3q/
it's not perfect though, the change event will be fired multiple times with each click, and if no options were selected, it could left blank. Need more code to filter these behaviour.

Vue v-for after condition using vuex

While I am trying to create a form I encountered this problem which I don't have any solution.
There is a Vuex data on Vehicles Make and Model of vehicle, now once the make is selected, I want the other form to loop through the selected Make and find other models... something like that.
Here is what I did so far:
cars.js - (vuex module)
const state = {
make: [
{
name: 'Audi',
carid: '1',
models: [
{
modelid: '1.1',
name: 'A7',
},
{
modelid: '1.2',
name: 'A8',
},
],
},
{
name: 'BMW',
carid: '2',
models: [
{
modelid: '2.1',
name: '5 Series',
},
{
modelid: '2.2',
name: '7 Series',
},
],
},
],
}
Cars.vue
<template>
<div class="labelos">
<div class="label-name">
<h4>Car make:</h4>
</div>
<div class="label-body">
<label for="car-make">
<select v-model="selectedType" name="carmake" required>
<option value=""></option>
<option v-for="(cars, index) in cars.make" :key="index" :value="cars.carid">{{ cars.name }}</option>
</select>
</label>
</div>
</div>
<div class="labelos">
<div class="label-name">
<h4>Car model:</h4>
</div>
<div class="label-body">
<label for="car-model">
<select>
<option value=""></option>
<option v-for="(model, index) in cars.make" :key="index" :value="cars.carid">{{ model.carid }}</option>
</select>
</label>
Model:
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'cars',
data() {
return {
selectedType: '',
selectedCity: '',
};
},
methods: {
},
components: {
Headers,
Footers,
},
computed: {
...mapState([
'cities', 'cars',
]),
},
};
</script>
So as you can see on first label I am looping through makes, and once a car make is selected that carid is saved on selectedType, now how is that possible to load second dropdown according to that selection, so if carid 1 is selected, the list will load car models available on given carid (in this example carid 1)
Looking forward to hear from someone, I am stuck here.. I don't know any solution how to do this... this is so far I have done
Cheers
You should create a computed property which returns model options based on the value of the selected make type. Then you can bind to that and it will automatically update whenever the selected make changes:
models() {
if (this.selectedType) {
return this.cars.make.find((car) => car.carid === this.selectedType).models;
}
}
Here's a working example:
const store = new Vuex.Store({
state: {
cars: {
make: [{
name: 'Audi',
carid: '1',
models: [
{ modelid: '1.1', name: 'A7' },
{ modelid: '1.2', name: 'A8' },
]
}, {
name: 'BMW',
carid: '2',
models: [
{ modelid: '2.1', name: '5 Series' },
{ modelid: '2.2', name: '7 Series' }
],
}]
}
}
})
new Vue({
el: '#app',
store,
data() {
return {
selectedType: '',
};
},
computed: {
...Vuex.mapState(['cars']),
models() {
if (this.selectedType) {
return this.cars.make.find((car) => car.carid === this.selectedType).models;
}
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.min.js"></script>
<div id="app">
<h4>Car make:</h4>
<select v-model="selectedType" name="carmake" required>
<option value=""></option>
<option v-for="(cars, index) in cars.make" :key="index" :value="cars.carid">{{ cars.name }}</option>
</select>
<h4>Car model:</h4>
<select>
<option value=""></option>
<option v-for="(model, index) in models" :key="index" :value="model.modelid">{{ model.name }}</option>
</select>
</div>
Working example with your data:
const state = {
make: [
{
name: 'Audi',
carid: '1',
models: [
{modelid: '1.1', name: 'A7'},
{modelid: '1.2', name: 'A8'}
]
}, {
name: 'BMW',
carid: '2',
models: [
{modelid: '2.1', name: '5 Series'},
{modelid: '2.2', name: '7 Series'}
]
}
]
}
new Vue({
el: '#app',
data: {
state: state,
selected: 0
},
computed: {
models () {
var maker = this.state.make.find(m => m.carid === this.selected)
return maker ? maker.models : []
}
}
})
<div id="app">
<select v-model="selected">
<option value="0" selected>Choose maker</option>
<option
v-for="maker in state.make"
:key="maker.carid"
:value="maker.carid"
>{{ maker.name }}</option>
</select>
<br>
<select>
<option value="0" selected>Select model</option>
<option
v-for="model in models"
:key="model.modelid"
:value="model.modelid"
>{{ model.name }}</option>
</select>
</div>
<script src="https://unpkg.com/vue#2.5.3/dist/vue.min.js"></script>
If you can, change 'modelid' to simple integers - 1, 2, etc., at least. And if you can and you know how to do it, change your data structure - divide makers and models to separate arrays/objects.
Here's a plugin for this specific task you're trying to accomplish: vue-dependon.
It hasn't been updated for 1-2years, but I think that you can check its source code and see how it works.
UPDATE:
All you need from the sourcecode is the loadOptions function and the code between L83 and L105.
You can adapt that code to your needs.

Vue.js filter a list

I am new to Vue.js. I'm trying to display a filtered list based on the value of another object. The select sets the value of the object called activeSet. That value is the key in the sets object. If that keys value is greater than 0 I want to display it in the list and sort by it. Here is my code:
JS
var vm = new Vue({
el: '#app',
data: {
activeSet: 'all',
computed: {
activeContents: songs.filter(function(song) {
return (song[activeSet] > 0);
})
},
songs: {
a: {
title: 'Hound Dog',
all: 0,
set1: 2
},
b: {
title: 'Long Gone',
all: 1,
set1: 0
},
b: {
title: 'Novermber Rain',
all: 2,
set1: 3
}
},
sets: {
all: {
name: 'All Songs'
},
set1: {
name: 'Acoustic'
}
}
}
})
HTML
<div id="app">
<select v-model="activeSet">
<option v-for="(set,key) in sets" :value="key">{{set.name}}</option>
</select>
<ul id="menu">
<li v-for="(song,key) in activeContents" :id="key" :is-active="song.show" :key="key" #click="activeSong=key">{{ song.title }}</li>
</ul>
</div>
Fiddle
Here is the fiddle
Sidenote: If the value is above 0 it needs to be included and then sorted by that value. I haven't even dived into sorting yet so bonus points if you can sort by the value of those that are greater than 0.
Computed properties are not defined in data, they have their own section in the Vue definition. Additionally, you should use this to reference data properties inside the computed.
If I understand the question correctly, this should work for sorting and filtering.
console.clear()
var vm = new Vue({
el: '#app',
data: {
activeSet: 'all',
songs: {
a: {
title: 'Hound Dog',
all: 0,
set1: 2
},
b: {
title: 'Long Gone',
all: 3,
set1: 0
},
c: {
title: 'Novermber Rain',
all: 2,
set1: 3
}
},
sets: {
all: {
name: 'All Songs'
},
set1: {
name: 'Acoustic'
}
}
},
computed: {
activeContents(){
// setup
let set = this.activeSet
let filter = k => this.songs[k][set] > 0
let sorter = (a,b) => this.songs[a][set] - this.songs[b][set]
// filter and sort
let selectedKeys = Object.keys(this.songs)
.filter(filter)
.sort(sorter)
// build new object
return selectedKeys.reduce((acc, k) => {
acc[k] = this.songs[k]
return acc
}, {})
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<select v-model="activeSet">
<option v-for="(set,key) in sets" :value="key">{{set.name}}</option>
</select>
<ul id="menu">
<li v-for="(song,key) in activeContents" :id="key" :is-active="song.show" :key="key" #click="activeSong=key">{{ song.title }}</li>
</ul>
</div>

AngularJS - repeating over an array of IDs, how can I look them up?

Rather vague question, hopefully this will explain better. I'm grabbing the following JSON:
{
items: [
{ name: "item 1", options: [1, 2, 3] },
{ name: "item 2", options: [2, 3] },
{ name: "item 3", options: [1] }
],
options: [
{ id: 1, color: "red" },
{ id: 2, color: "blue" },
{ id: 3, color: "yellow" }
]
}
And I'm repeating over the items like so:
<div data-ng-repeat="item in items">
<span>{{ name }}</span>
<ul>
<li data-ng-repeat="i in item.options">{{i}}</li>
</ul>
</div>
Ideally, I'd like to be able to access the color parameter (and others) in my second ng-repeat loop (rather than just the number primitive). What's the best way to do that? Should I do a map on the options array for each item when I'm initializing, and turn each index into the full object (with id and color)? Or can I pass the index to the controller, do a look up and extend my scope with the option I looked up?
One option would be to use a function to get the options associated with your item.
http://plnkr.co/edit/RZvH2lWilQaIUJTq4DUL?p=info
<body ng-controller="MainCtrl">
<div data-ng-repeat="item in items">
<span>{{ name }}</span>
<ul>
<li data-ng-repeat="i in getOptions(item.options)">{{i.color}}</li>
</ul>
</div>
</body>
app.controller('MainCtrl', function($scope) {
$scope.items = [
{ name: "item 1", options: [1, 2, 3] },
{ name: "item 2", options: [2, 3] },
{ name: "item 3", options: [1] }
];
$scope.options= [
{ id: 1, color: "red" },
{ id: 2, color: "blue" },
{ id: 3, color: "yellow" }
];
$scope.getOptions = function(options) {
var rv = [];
angular.forEach($scope.options, function(option, idx){
if(options.indexOf(option.id) > -1)
{
rv.push(option);
}
});
return rv;
}
});
<li data-ng-repeat="i in item.options">{{options[i-1].color}}</li>
Edit: If options array is unsorted:
<li data-ng-repeat="i in item.options">{{getColor(i)}}</li>
controller.js:
$scope.getColor = function(i) {
for(var index=0, len=$scope.options.length; index<len; index++) {
if ($scope.options.index.id===i) return $scope.options.index.color;
}
}
The best way is to return the options part in your JSON as this structure
options: {
"1": "red",
"2": "blue",
"3": "yellow"
}
Then you can access it by the key since it is a JavaScript object:
<li data-ng-repeat="i in item.options">{{options[i]}}</li>

Categories