Item undefined - by page load but later by select event work without problem - vue.js

I need to set "message" on page load and later update it by select box from array in data. But by page load, the item is undefined - what make I wrong? Thank you for advice.
the code is as well on: https://repl.it/#DaBor/find#index.html
let vueApp = new Vue({
el: '#app',
data: {
selected: '1',
options: [
{id:1, projectNAME:"bobx", note:"note-bobx"},
{id:2, projectNAME:"danx", note:"note-danx"},
{id:3, projectNAME:"barb", note:"note-barb"},
],
message: "start"
},
methods: {
foo: function(){
var item = this.options.find(item => item.id === this.selected);
alert(item);
this.message = item.note;
},
},
mounted() {
this.foo();
},
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<select v-model="selected" v-on:change="foo" >
<option v-for="option in options" v-bind:value="option.id">
{{ option.projectNAME }}
</option>
</select>
<span>Selected id:{{ selected }}</span>
<br />
<span v-html="message"></span>
</div>

The find() callback compares item.id (a Number) to selected (a String):
var item = this.options.find(item => item.id === this.selected);
^ ^
Number String
Strict equality (===) compares both type and value. Since the two types are different in this case, the comparison would always be false, resulting in undefined being returned from find().
One solution is to convert selected to a Number, and use that to compare:
var selectedId = Number(this.selected);
var item = this.options.find(item => item.id === selectedId);
updated repl.it

Related

VueJS dynamic data + v-model

In my data object, I need to push objects into an array called editions.
data() {
return {
editions: []
}
}
To do this, I am dynamically creating a form based on some predetermined field names. Here's where the problem comes in. I can't get v-model to cooperate. I was expecting to do something like this:
<div v-for="n in parseInt(total_number_of_editions)">
<div v-for="field in edition_fields">
<input :type="field.type" v-model="editions[n][field.name]" />
</div>
</div>
But that isn't working. I get a TypeError: _vm.editions[n] is undefined. The strange thing is that if I try this: v-model="editions[n]"... it works, but I don't have the property name. So I don't understand how editions[n] could be undefined. This is what I'm trying to end up with in the data object:
editions: [
{
name: "sample name",
status: "good"
},
...
]
Can anyone advise on how to achieve this?
But that isn't working. I get a TypeError: _vm.editions[n] is undefined.
editions is initially an empty array, so editions[n] is undefined for all n. Vue is essentially doing this:
const editions = []
const n = 1
console.log(editions[n]) // => undefined
The strange thing is that if I try this: v-model="editions[n]"... it works
When you use editions[n] in v-model, you're essentially creating the array item at index n with a new value. Vue is doing something similar to this:
const editions = []
const n = 2
editions[n] = 'foo'
console.log(editions) // => [ undefined, undefined, "foo" ]
To fix the root problem, initialize editions with an object array, whose length is equal to total_number_of_editions:
const newObjArray = n => Array(n) // create empty array of `n` items
.fill({}) // fill the empty holes
.map(x => ({...x})) // map the holes into new objects
this.editions = newObjArray(this.total_number_of_editions)
If total_number_of_editions could change dynamically, use a watcher on the variable, and update editions according to the new count.
const newObjArray = n => Array(n).fill({}).map(x => ({...x}))
new Vue({
el: '#app',
data() {
const edition_fields = [
{ type: 'number', name: 'status' },
{ type: 'text', name: 'name' },
];
return {
total_number_of_editions: 5,
editions: [],
edition_fields
}
},
watch: {
total_number_of_editions: {
handler(total_number_of_editions) {
const count = parseInt(total_number_of_editions)
if (count === this.editions.length) {
// ignore
} else if (count < this.editions.length) {
this.editions.splice(count)
} else {
const newCount = count - this.editions.length
this.editions.push(...newObjArray(newCount))
}
},
immediate: true,
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.min.js"></script>
<div id="app">
<label>Number of editions
<input type="number" min=0 v-model="total_number_of_editions">
</label>
<div><pre>total_number_of_editions={{total_number_of_editions}}
editions={{editions}}</pre></div>
<fieldset v-for="n in parseInt(total_number_of_editions)" :key="n">
<div v-for="field in edition_fields" :key="field.name+n">
<label>{{field.name}}{{n-1}}
<input :type="field.type" v-if="editions[n-1]" v-model="editions[n-1][field.name]" />
</label>
</div>
</fieldset>
</div>

Vue multiple select form independent from each other

I need a solution how to make as many select form as my option data length and each form must be independent from each other. It means that if i chooce selected value in one form it must do not overide selected value in others form. Also i need pre-set selected value in each form (for the first form it need to show selected first choice, for second = second choice and so on.)
var app = new Vue({
el: "#app",
delimiters: ["[[", "]]"],
data: {
selected: '',
options: [
{ id: 20 , supp_name: 'test1' },
{ id: 21 , supp_name: 'test2' },
{ id: 34 , supp_name: 'supertest' },
]
},
})
<div margin="20px" v-for='option in options'>
<form action="">
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.supp_name">
[[option.supp_name]]
</option>
</select>
<span>Chosen: [[selected]]</span>
</form>
</div>
It display 3 forms but when I make a choice in on one form it override all forms. How could it be fixed?
You will need as many selected references as there are forms.
For example
var app = new Vue({
el: "#app",
delimiters: ["[[", "]]"],
data: {
selected: [], // make selected an array
options: [{"id":20,"supp_name":"test1"},{"id":21,"supp_name":"test2"},{"id":34,"supp_name":"supertest"}]
},
watch: {
options: {
immediate: true,
handler (options) {
// initialise to the "supp_name" from options
this.selected = options.map(({ supp_name }) => supp_name)
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<div v-for="(_, i) in selected">
<form action="">
<select v-model="selected[i]">
<option v-for="option in options" :value="option.supp_name">
[[ option.supp_name ]]
</option>
</select>
<span>Chosen: [[ selected[i] ]]</span>
</form>
</div>
<pre>selected = [[ selected ]]</pre>
</div>
This synchronises the selected array to be the same length as options then binds each form's <select> to the array index.

select value unset after click button

select with options are generated dynamically.
When button 'GO' were click select options generates again with different data. But the issue is when user select option 1 and click 'go' automatically option 1 is selected from next select, how to clear that?
my code:
<select v-model="key" #change="onChange($event)">
<option v-for="(option, index) in passSelect" :value="index" v-if="option!==null" :data-foo="option" #click="onChange($event)" #click="onClick($value)">
{{ option }}
</option>
</select>
<button #click="onChildClick" class="button">Go -></button>
methods:
onChildClick() {
this.counter++;
this.loadAgain();
},
getSelect(){
this.passSelect = this.checkExist();
},
onChange(event) {
this.selectedIndex = event.target.value;
this.selectedValue = event.target.options[event.target.value].dataset.foo;
},
loadAgain(){
this.getSelect();
},
Selection is determined by the v-model, which is using the property key. As you're using the index for the value this will cause the option with the same index as the previous list to be selected.
You need to clear the value of key to reset the <select>:
new Vue({
el: '#app',
data () {
return {
counter: 0,
key: null,
passSelect: [],
selectedIndex: null,
selectedValue: null
}
},
created () {
this.getSelect();
},
methods: {
onChildClick () {
this.counter++;
this.loadAgain();
},
getSelect () {
this.key = null;
this.selectedIndex = null;
this.selectedValue = null;
this.passSelect = getOptionsList();
},
onChange (event) {
const value = event.target.value;
if (value) {
this.selectedIndex = event.target.value;
this.selectedValue = event.target.options[event.target.value].dataset.foo;
}
},
loadAgain () {
this.getSelect();
}
}
})
function getOptionsList () {
return ['Red', 'Yellow', 'Green', 'Brown', 'Blue', 'Pink', 'Black'].filter(c => {
return Math.random() > 0.4;
});
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<select v-model="key" #change="onChange($event)">
<option v-for="(option, index) in passSelect" :value="index" v-if="option !== null" :data-foo="option">
{{ option }}
</option>
</select>
<button #click="onChildClick" class="button">Go -></button>
<p>counter: {{ JSON.stringify(counter) }}</p>
<p>key: {{ JSON.stringify(key) }}</p>
<p>selectedIndex: {{ JSON.stringify(selectedIndex) }}</p>
<p>selectedValue: {{ JSON.stringify(selectedValue) }}</p>
</div>
I've also cleared selectedIndex and selectedValue to try to keep the data consistent.
Some other notes:
I've got rid of the 2 click listeners on the <option>. Not really sure what they were for but you shouldn't have had 2 listeners for the same event on the same element.
key and selectedIndex are almost the same thing. The only difference is that selectedIndex ends up being a string whereas key is a number. Not clear why you aren't just using selectedIndex for your v-model directly.
I don't know what checkExist does but passSelect feels like it should be a computed property from the code provided.
There shouldn't be any need to use data-foo to pass the option to the listener. You can get the relevant option directly from the data once you have the index. All of which assumes you actually need the index, otherwise you could just bind the value directly to the string option.

How to get index data from object used in v-for in select

I have a component involving a select element. Below, opts is an array of objects.
Vue.component('atcf-select', {
props: [
'opts',
],
data() {
return {
element_index: '',
};
},
template: `
<div>
<select #change="onChange(opt,index)">
<option v-for="(opt,index) in opts">
{{ opt.text }} {{opt.index}}
</option>
</select>
</div>
`,
methods: {
onChange(opt,index) {
//Do something with opt and index...
}
}
};
The problem is obviously I cannot get the selected opt object and its index, and use it as a parameter for onChange method. What is the correct way to get the selected option's index and object?
You won't be able to pass the opt or index values to the change listener on the select element because it is outside the scope of the v-for.
If you don't specify any parameters for the onChange handler, Vue will implicitly pass an event object. From there, you can get the selectedIndex value via e.target.selectedIndex.
Here's an example:
new Vue({
el: '#app',
data() {
return {
opts: [
{ value: 'a', text: 'A' },
{ value: 'b', text: 'B' },
{ value: 'c', text: 'C' },
]
}
},
methods: {
onChange(e) {
let index = e.target.selectedIndex;
let option = this.opts[index];
console.log(index, option);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.8/vue.min.js"></script>
<div id="app">
<select #change="onChange">
<option v-for="(opt, index) in opts" :key="index" :value="opt.value">
{{ opt.text }}
</option>
</select>
</div>
You can use v-model
{{ option.value }} - {{ option.text }}
Index of {{valeureSelectionnee}} is : {{ IndexValeureSelectionnee }}

Retrieving text of select element

When binding a <select> element using vue.js's v-model, how would you get the selected option text as opposed to the selected option value?
In HTML:
<select v-model="selected" options="myOptions"></select>
In JS:
myOptions: [{ text: 'Blue', value: '1' }, { text: 'Green', value: '2' }]
What I would want to retrieve is both the text 'Blue' as well as the value '1' by doing something like {{ selected.text }} or {{ selected.value }}. However, you can only do {{ selected }} which returns the selected value by default.
Ref: Vue.js guide for Dynamic Select Options
You can just use a filter, like this:
html:
<div id='vm'>
Formatted value:<b> {{city | cityFormatter}} </b><br/>
<br/>
<select v-model="city" options="cities"></select>
</div>
js:
var vm = new Vue({
el: '#vm',
data: {
city: 'city1',
cities: [{text: 'Toronto', value: 'city1'},
{text: 'Orleans', value: 'city2'}]
},
filters: {
cityFormatter: function(val) {
var newVal = '';
this.cities.map(function(el){
if (val == el.value){
newVal = el.value + ' ' + el.text;
}
});
return newVal;
}
}
});
Working example:
http://jsfiddle.net/qfy6s9Lj/9/
Actually, you could try combine jquery or just native js code
The solution with jQuery
html:
<div id='example'>
<select v-model="selected" options="myOptions"></select>
</div>
js:
var vm = new Vue({
el: '#example',
data: {
...
},
computed: {
selectedtext: {
cache: false,
//get selectedtext by jquery
get: function(){ return $(this.$el).find(":selected").text();}
}
},
});
The solution without jquery
html:
<div id='example'>
<select ref="ddl" v-model="selected" options="myOptions"></select>
</div>
js:
var vm = new Vue({
el: '#example',
data: {
...
},
computed: {
selectedtext: {
cache: false,
//get selectedtext directly
get: function(){
var ddl = this.$refs.ddl;
return ddl.options[ddl.selectedIndex].text;
}
}
},
});
Moreover, you could create a component for reusing logic and achieve the purpose of accessing the selected value by {{ selected.text }} or {{ selected.value }}.
An answer for Vue 2+
I was interested to come across this question as I'm currently evaluating Vue and React, and researching the comparative ease of getting current selection (not just dropdowns, but the UI generally).
What I found was that things have changed a lot since these posts in May-July 2015, when the latest version of Vue was 0.12. The JSFiddle in #swift's answer still works today because it pulls in Vue 0.12.
Using today's Vue, currently version 2.6, I found a solution that's relevant to people facing the same question today. It's interesting that using 2.6, the markup under discussion won't even get as far as initializing the options:
<select v-model="selected" options="myOptions"></select>
After digging into this, I found that although options is a valid HTML DOM property of the select element, and hence is accessible from JavaScript, it seems Vue no longer supports initializing it in markup like this. Instead, we add traditional HTML option elements. Per https://v2.vuejs.org/v2/guide/forms.html:
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<span>Selected: {{ selected }}</span>
On first sight, this change seems a backward step. But in fact, remembering that we can use v-for and v-bind, I think it makes things more flexible. To demonstrate why I think that, I will first show the example given at the same linked page:
HTML
<select v-model="selected">
<option v-for="option in options" v-bind:value="option.value">
{{ option.text }}
</option>
</select>
<span>Selected: {{ selected }}</span>
JS
new Vue({
el: '...',
data: {
selected: 'A',
options: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
]
}
})
It can be seen in the HTML that this binds the selected value of the dropdown to the Vue instance's selected property (using v-model="selected"), binds the individual option values to the value of each option (using v-bind:value="option.value"), and finally binds the individual option texts to the text that will be displayed (using {{ option.text }}).
It's only a small step further to bind selected to a different option property, be it text, id or whatever properties your option object may have, or--and here's the thing--to the option object itself. Being able to access the option itself as the selected value means we can access all of its properties, rather than only the property we chose to bind to:
HTML
<div id='vm'>
<select id="ddl1" v-model="ddl1selecteditem">
<option v-for="option in options1" v-bind:value="option">
{{ option.txt }}
</option>
</select>
<span>selected item: text='{{ ddl1selecteditem.txt }}', id={{ ddl1selecteditem.id }}</span>
</div>
JS
var vm = new Vue({
el: '#vm',
data: {
options1: [
{ txt: 'One', id: 1 },
{ txt: 'Two', id: 2 },
{ txt: 'Three', id: 3}
],
ddl1selecteditem: {}
}
});
vm.ddl1selecteditem = vm.options1[0];