Select option entries are only shown on the first select attempt - vue.js

I am using vuetify and an I'm trying to populate a select box...
...using this JSON Object:
[
{
"configurator": {
"group": {
"property": [
{
"id": "STATUS",
"value": [
{
"id": "OK",
"text": "OK"
},
{
"id": "NOK",
"text": "not OK",
"selected": "true"
}
]
}
]
}
}
}
]
...and I'm trying to render this component:
<v-list v-for="(item, i) in jsonObjPruef.configurator.group.property" :key="i">
<v-select
v-model="item.value"
:items="item.value"
:label="item.text"
/>
</v-list>
Page loads without warnings and errors and I can select from the expected values ("OK", "not OK").
While choosing e.g. "OK" the following warning is displayed:
[Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, got String with value "OK".
And now if I'm trying to select again I only can pick the value I chose before ("OK").
The select option entries are only shown on the first select attempt.
Any idea what I'm missing here?

tl;dr: v-model means: "where user selection is saved". If you save it over the same exact property which holds your options, the options are gone and the <v-select> is broken.
v-model replaces jsonObjPruef.configurator.group.property with the selected option, when you select it. Which makes the <v-select> no longer have items.
You should specify a different v-model - a model property in which you store the selection, (i.e: results):
<v-list v-for="(item, i) in jsonObjPruef.configurator.group.property" :key="i">
<v-select
v-model="results[i]"
:items="item.value"
:label="item.text"
/>
</v-list>
In data, you need to initialize results : {}.
Now results will hold the selected results and you can place a watcher on it to trigger additional functionality when it changes. It doesn't have to be an object, it could be an array. It depends on what you currently have.
Obviously, you can rename results to something less generic, which makes more sense in your specific example.
If you need more help, please add a mcve.

Related

Cannot read property of undefined after fetching data?

p {{ points.returnPoints }}
returns in my html
[ { "description": null, "id": "ade5e33e-658f-4a9d-84f9-590357600054", "name": "Starting Point", "position": { "lat": -0.00027894973753803614, "lon": -0.01857362687587738 }, "radius": 100, "sort": 0 } ]
but, whenever I do on my button v-if="points.returnPoints[0].name != Starting Point`"
I am getting this error Uncaught (in promise) TypeError: Cannot read property 'name' of undefined
Why am I getting this error? I must hide visibility of my item, based on first array element's name and I am not able to do it because of this error. How do I fix this?
In my component I am calling GET request for the pointList array.
My getter:
getters: {
returnPoints(state) {
let sortState = [...state.pointList.collection]
return sortState.sort((a,b) => a.sort - b.sort)
},
}
Cannot read property of undefined after fetching data?
In fact, it happens before the data is fetched, because the data is not present when the component gets rendered: points.returnPoints[0] is undefined and trying to access the name property results in an error.
Try this instead:
<div v-if="points.returnPoints.length && points.returnPoints[0].name !== 'Starting Point'"> ... </div>
The code above assumes that points.returnPoints is an array. If it's empty, the second condition won't be checked and it won't result in an error.

vue nested event fired but parent one is omited

I'm using this library for autocomplete select, and in my logic, I also want to trigger require validation (from vuelidate library) on the fly. I want to trigger the require validation rule when the user focuses out the input and when the user selects some value from the suggestion list.
And for some reason when I'm selecting a value by the first time from the suggestion list it's triggering focusout event not a select, probably b-z list is out of input focus, but when I'm selecting an item from the list by the second time it's triggering select event not focusout (as it should). NOTE: vue trigger select event only when I select value for the second time, first, third and all next times it's always triggering focusout event.
Question: why it's not triggering select event and how to force triggering it??
Code snippet:
<vue-simple-suggest v-model="city.searchInput"
:debounce="300"
:min-length="2"
display-attribute="name"
value-attribute="id"
:list="getCity"
:filter-by-query="true"
#select="citySelect">
<input type="search"
class="form-control"
placeholder="Place of birth"
#focusout="cityError" />
</vue-simple-suggest>
methods: {
cityError() {
console.log('cityError');, // <== when I select some item from suggestions list by the first time this event triggered
},
citySelect() {
console.log('citySelect'); // <== when I'm clicking by input second time (the suggestions list already loaded) and select some (or same) value from suggestions list this event triggered
},
getCity() {
// here should be ajax call, but for now just dummy data
return [{
"id": "691b237b0322f28988f3ce03e321ff72a12167fd",
"name": "Paris",
"lat": -33.8599358,
"lng": 151.2090295
},
{
"id": "691b237b0322f28988f3ce03e321ff72a12167fs",
"name": "New York",
"lat": -33.8599358,
"lng": 151.2090295
},
]
}
}
I open an issue on github https://github.com/KazanExpress/vue-simple-suggest/issues/353 (it has detailed information and small video which showing the issue, you can check browser console), but I'm not sure it's related to the library.
Thanks in advance!
I saw in the documentation of the library that it allows blur event. Could you try #blur in vue-simple-suggest component instead of #focusout in the input?
Otherwise you can run a controller in the #select="checkCitySelection" to avoid the #focusout event in the input:
checkCitySelection(citySelected) {
this.$v.city.value.$touch();
if (this.$v.city.value.$invalid) this.cityError()
else this.citySelect();
}

Vue v-for keys and values

I have the following JSON:
{
"data": [
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Neutral",
"type": "Article"
},
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Negative",
"type": "Article"
},
{
"title": "a title here",
"news_url": "https://someurl",
"sentiment": "Neutral",
"type": "Article"
}
]
}
I have defined my data object 'news' like so:
data() {
return {
news: [],
};
},
Now I am trying to v-for through these values so that I can get 3 divs each with the title value.
I am trying the following but I really don;t have much of a clue:
<div v-for = "title in news">
{{ title }}
</div>
I get the error: Elements in iteration expect to have 'v-bind:key' directives.
Any idea what I am doing wrong?
Thanks!
Vue documentation indicate:
It is recommended to provide a key attribute with v-for whenever
possible, unless the iterated DOM content is simple, or you are
intentionally relying on the default behavior for performance gains.
Since it’s a generic mechanism for Vue to identify nodes, the key also
has other uses that are not specifically tied to v-for, as we will see
later in the guide.
 
Don’t use non-primitive values like objects and arrays as v-for keys.
Use string or numeric values instead.
Then you should use the key directive binding it with string or numeric value like this:
<div v-for = "(title, index) in news" :key="index">
{{ title }}
</div>

get selected object from options loop

I'm trying to find a way to bind array of objects within Vue select-element. The case is somewhat as follows:
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]},
currentForm: {
something: "foo",
else: "bar",
ideaCode: "01A",
text: "option 1"
}
];
... and in HTML ...
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm(???)">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
Basically I need to be able to track which object user selects, trigger my own change-event, all the while having binding with a single key from another object... selected value / reference-key should be separated from user-selected option/object. Note: currentForm is not same object-type as option! It only contains some of those properties which option happens to have, and which I'm trying to transfer to options by triggering change-event for user-selection.
The problem is I haven't figured out how to pass currently selected value for the function OR how to write something like:
<select v-model="selectedIdea" #change="setCodeAndLabelForForm" :track-by="currentForm.ideaCode">
<option v-for="i in ideas" value="i">{{ i.text }}<option>
</select>
One possible (and working) approach is:
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm">
<option v-for="i in ideas" value="i.ideaCode">{{ i.text }}<option>
</select>
setCodeAndLabelForForm: function() {
var me = this;
this.ideas.forEach(function(i) {
if(i.ideaCode == me.currentForm.ideaCode) {
me.currentForm.ideaCode = i.selectedIdea.ideaCode;
me.currentForm.text = i.text;
... do stuff & run callbacks ...
}
});
}
... but it just seems terrible. Any better suggestions?
You can implement like this:
Create empty object data to track the selected value:
currentForm: {}
Watch currentForm on the model and pass the selected object:
<select v-model="currentForm" #change="setCodeAndLabelForForm(currentForm)">
Pass in the selected value in option: (you were doing right in this step, but I just changed i to idea as it's little confusing looping index)
<option v-for="idea in ideas" :value="idea">{{ idea.text }}<option>
Apply your method:
setCodeAndLabelForForm(selected) {
// Now, you have the user selected object
}
A little bit better workaround: use index in v-for
<select v-model="selIdeaIndex" #change="setCodeAndLabelForForm">
<option v-for="(i,idx) in ideas" value="idx">{{ i.text }}<option>
</select>
For the js:
data: {
selIdeaIndex:null,
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2 , props: [{ details: "something" }]}
]
},
methods:{
setCodeAndLabelForForm: function() {
var selIdea = this.ideas[this.selIdeaIndex];
//Do whatever you wanna do with this selIdea.
}
}
I don't know if this is the best solution, but I solve this problem using computed properties like this:
In the JavaScript file (ES6):
data () {
return {
options: [
{ id: 1, text: "option 1" },
{ id: 2, text: "option 2" }
],
selectedOptionId: 1
}
},
computed: {
selectedOption () {
return _.find(this.options, (option) => {
return option.id === this.selectedOptionId
});
}
}
In the HTML file:
<select v-model="selectedOptionId">
<option v-for="option in options" :value="option.id" :key="option.id">{{ option.text }}<option>
</select>
The '_' symbol is a common JavaScript library called Lodash and I highly recommend the usage. It can make you save some precious time.
If you know your options will only come from that v-for="i in ideas" then the <option> indexes will be the same as the item indexes.
Thus <select>.selectedIndex will be the index of the selected this.item.
new Vue({
el: '#app',
data: {
ideas: [
{ id: 1, code: "01A", text: "option 1", props: [] },
{ id: 2, code: "02A", text: "option 2" , props: [{ details: "something" }]}
],
currentForm: {ideaCode: "01A", text: "option 1"}
},
methods: {
setCodeAndLabelForForm: function(selectedIndex) {
var selectedIdea = this.ideas[selectedIndex];
this.currentForm = {ideaCode: selectedIdea.code, text: selectedIdea.text};
}
}
})
<script src="https://unpkg.com/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<select v-model="currentForm.ideaCode" #change="setCodeAndLabelForForm($event.target.selectedIndex)">
<option v-for="i in ideas" :value="i.code">{{ i.text }}</option>
</select>
<br> currentForm: {{ currentForm }}
</div>
Differences from yours: #change="setCodeAndLabelForForm($event.target.selectedIndex)" and the setCodeAndLabelForForm implementation.
A humble way is using $ref.
There is a solution using $ref and #change.
Vue.js get selected options' raw object
It's been a while since I asked this question and there have been good suggestions for handling this situation. I cannot remember the exact business-case presented here, but just by a quick glance it looks like I couldn't figure out how to set/initialize right selection afterwards, because handling just the #change event is childs play -- it's the pairing of one single value against list of object-based-options which is harder. What I was most likely looking for was something AngularJS used to have (track-by -property, which matches any given value against selected-option).
Personally now-a-days I would separate UI-logics instead of trying to force 'em to blend together. Most viable approach for myself would be handling list of options and the selected option as one logical area (ie. data: { list: [...options], selectedIdea: Object }) and separate the "currentForm"-object from selection. Let's break this out:
selectedIdea is something which needs to trigger change into currentForm-object. It's not any kind of hybrid-model, it's just a plain object, one of the available selections, pure and simple.
... and once again: Whenever selectedValue === one of the options, the select-dropdown is automatically set to right selection.
"currentForm"-object has a property which can be used to set the selectedIdea. In this case it's the "ideaCode". This ideaCode doesn't automatically do any pairing or such Component logic needs to represent the rules, which trigger selecting the correct option, which matches "ideaCode".
Just an extra-though: selectedIdea and currentForm-object are two different logical elements. They could be even separated to different components if one would want do, and in some cases it's really good thing to separate 'em.
So by these statements I guess I would change my select's v-model to be exactly what it's supposed to be: One of the selected objects (change v-model="currentForm.ideaCode" into v-model="selectedIdea" or such). Then I would simply add watcher for that selectedIdea and make any alterations to currentForm-object from there.
How about initializing that option by currentForm.ideaCode ? Do one of the following on create-method:
Iterate list of available options. When you find option where currentForm.ideaCode == option.code => this.selectedIdea = option
... or use ecmascript find-method to do the same
... or use underscore/lodash find-method to do the same
Another way would be by using computed value, as suggested Augusto Escobar. Also $ref would work, as suggested by feng zhang, but this approach would still require solution for initializing correct option afterwards (when loading editor with initial values). Thanks to Bhojendra Rauniyar as well -- you were right all along, but I just couldn't comprehend the answer as I couldn't have figured out how to backtrack initial selection.
Thanks for all the suggestions over the year!

V-for inside v-for in Vue.js

I'm trying to get object items from inside a parent object using a v-for inside another v-for in Vue.js.
Data structure:
flights: [
{
"airline": "BA",
"airport": "EBJ",
"status": {
"_code": "A",
"_time": "2018-03-02T14:19:00Z"
}
},
etc....
]
<div v-for="flight in flights">
<p>{{flight.airline}}</p>
<p>{{flight.airport}}</p>
<div v-for="st in flight.status">
<p>{{st._code}}</p> // should return 'A'
<p>{{st._time}}</p> // should return '2018-03-02T14:19:00Z'
</div>
</div>
However, neither st._code or st._time return anything. st returns both values ("A" and "2018-03-02T14:19:00Z").
Any idea on how to return the single values inside the status object?
It is possible to use v-for on an object, as you're trying to do with status, but the syntax is slightly different; in cases where iterating over an object is useful you'll generally want to include the key as well as the value:
<div v-for="(val, key) in flight.status">
<p>{{key}}: {{val}}</p>
</div>
would output
<p>_code: A</p>
<p>_time: 2018-03-02T14:19:00Z</p>
In your case you already know the specific keys you want, so it would be easier to not use v-for and instead just use e.g {{flight.status._code}}.
Unless there can be more than one "status" per flight, there's no good reason to wrap status in an array. This will work with your existing data structure:
<div v-for="flight in flights">
<p>{{flight.airline}}</p>
<p>{{flight.airport}}</p>
<p>{{flight.status._code}}</p>
<p>{{flight.status._time}}</p>
</div>
The reason you are not seeing the expected output is because, of this line:
<div v-for="st in flight.status">
That means you are expecting vue to iterated throught this:
"status": {
"_code": "A",
"_time": "2018-03-02T14:19:00Z"
}
and the above is an object, not an array ... so unless status is an array, it won't work.
If you expect your code to work, try changing your array to this:
flights: [
{
"airline": "BA",
"airport": "EBJ",
status: [{
"_code": "A",
"_time" : "2018-03-02T14:19:00Z"
}]
}
]
working demo:
https://jsfiddle.net/943bx5px/82/