Passing boolean value to v-for loop - vue.js

How can I access/convert element.varName string in v-for loop to be passed as variable name to get the current value of this boolean.
In the following case:
<div v-for="(element, index) in elements" :key="index" :class="{included : element.varName, 'cur-el':selected==element.shortName}">
<div v-html="element.icon"></div>
{{element.name}}
</div>
el1: false,
el2: false,
selected: undefined,
elements: [
{
name: 'element 1',
icon: `<svg>
<path></path>
<rect></rect>
</svg>`,
shortName: 'el1',
varName: this.el1
},
/...
]
How can my included class be a boolean value instead of the actual string, initially I tried with the shortName used accessing it as follow:
element.shortName
which didn't work, also tried:
[element.shortName]
as well as:
this[element.shortName]
None of which seems to work, so I tried including the actual reference to that variable by adding it in the object varName: this.el1, which also didn't work.
What am I doing wrong?

Since you're referencing a data property in other one you should define the second property as computed property :
data(){
return {
el1: false,
el2: false,
selected: undefined,
}
},
computed:{
elements(){
return [
{
name: 'element 1',
icon: `<svg>
<path></path>
<rect></rect>
</svg>`,
shortName: 'el1',
varName: this.el1
}
]
}
}

The reason why varName: this.el1 is not updated inside data option, is because it's not reactive.
You can read about that in Vue official documentation here.
Back to your question :
Try to assign the whole elements array in mounted() life cycle hook. So, that you can access this.el1.
Demo :
new Vue({
el:"#app",
data: {
el1: false,
el2: true,
elements: []
},
mounted() {
this.elements = [
{
name: 'element 1',
shortName: 'el1',
varName: this.el1
}, {
name: 'element 2',
shortName: 'el2',
varName: this.el2
}
]
}
});
.included {
background-color: yellow
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(element, index) in elements" :key="index" :class="{included : element.varName}">
{{element.name}}
</div>
</div>

Related

Nuxt Dynamic V-model

I'm having difficulty binding my custom input to data(). I've tried several combinations to try to get it working and so far only the placeholder seems to work. I created an array called questions and its content is dynamically rendered to the page. On page load, my code determines if this is either a user or business account and then sets the value of the questions array based on the result which works fine. I created a test function to test if the v-model binding is working but I get an empty alert. I find it strange that the placeholder works just fine but not the v-modal bind.
<template>
<section>
<form>
<BaseInput v-for="question in questions"
v-model="question.bind" :placeholder="question.placeholder"/>
</form>
<button #click="test"></button>
</section>
</template>
<script>
import BaseInput from '../BaseInput.vue'
export default {
components: {
BaseInput,
},
data(){
return{
firstName: '',
lastName: '',
commercialName: '',
businessName: '',
questions: [],
userQuestionsArray: [
{ bind: 'firstName', placeholder: 'First Name' },
{ bind: 'lastName', placeholder: 'Last Name' },
],
businessQuestionsArray: [
{ bind: 'commercialName', placeholder: 'Commercial Name' },
{ bind: 'businessName', placeholder: 'Business Name' },
]
}
}
},
methods: {
test(){
alert(this.password)
}
},
mounted() {
if(this.$store.state.userType === 'Personal'){
this.questions = this.userQuestionsArray;
}else {
this.questions = this.businessQuestionsArray;
}
},
computed: {
userType: {
get () {
return this.$store.state.userType
}
}
}
}
</script>
you cant use v-mode in v-for. you must use wrapper like template or tag over each input.
<template>
<section>
<form v-for="(question, index) in questions" :key="index">
<BaseInput v-model="question.bind" :placeholder="question.placeholder"/>
</form>
<button #click="test"></button>
</section>
</template>

Connect v-select with vuex: problem [object object]

I am trying to create a dropdown (v-select/q-select (using quasar)), which allows me to select from an array in my vuex-storage and then eventually save the selected item (content of it) in a variable. Currently I have no problem to access the vuex-storage, but face the problem, that the v-select expects a string and not an object.
My code looks like the following.
// vuex storage:
const state = {
savedsystems:
[
id: "1",
system: {...}
],
[
id: "2",
system: {...}
]
// example of the vuex storage out of my viewdevtools
systemsconstant: Object
savedsystems:Array[2]
0:Object
id:"first"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"5"
status:"not defined"
1:Object
id:"second"
system:Object
7a73d702-fc28-4d15-a54c-2bb950f7a51c:Object
name:"3"
status:"not defined"
88519419-8a81-48f1-a5e6-5da77291b848:Object
name:"9"
status:"defined"
}
// dropdown:
<q-select
outlined
dense
emit-value
:value="currentsystem"
:options="savedsystems"
label="selectsystem" />
// computed to get systems from vuex:
computed: {
savedsystems() {
return this.$store.getters['systemsconstant/getsavedsystems']
}
},
I used the following example https://codepen.io/sagalbot/pen/aJQJyp as inspiration and tried a couple of different setups stringifying resulting in nothing really.
If one would try to apply my case to a similar problem (v-select displays object Object), the mentioned formatlabel would be an object instead of a string.
Question:
How can I modify the (with a getter) imported array of objects "savedsystems", so it can be used both as label to select it and furthermore then to connect it properly to the values, so I can save the selected as a variable.
Or can I change something in my v-select, e.g. varying what comes behind :options/options?
I'd appreciate any help!
You should use the property option-label
<div id="q-app">
<div class="q-pa-md" style="max-width: 300px">
<div class="q-gutter-md">
<q-badge color="secondary" multi-line>
Model: "{{ model }}"
</q-badge>
<q-select filled v-model="model" :options="options" label="Standard" option-label="description"></q-select>
{{ model }}
</div>
</div>
</div>
JS:
new Vue({
el: '#q-app',
data () {
return {
model: null,
options: [
{
label: 'Google',
value: 'Google',
description: 'Search engine',
category: '1'
},
{
label: 'Facebook',
value: 'Facebook',
description: 'Social media',
category: '1'
},
{
label: 'Twitter',
value: 'Twitter',
description: 'Quick updates',
category: '2'
},
]
}
}
})
https://codepen.io/reijnemans/pen/bGpqJYx?editors=1010

Bootstrap-vue Checkbox, Check additional options when selected

What i want to do is also check the default option when a user checks one of the checkbox item.
i have created a snippet of the error i am encountering,
usually i thought its because of my nested components.
but i encountered the error
You may have an infinite update loop in watcher with expression "localChecked"
even on this simple code snippet.
vue js script
new Vue({
el: "#app",
data: {
application: [
{
app_name : 'Netflix',
app_default : 'videoshare_default',
options : [
{ text : 'Video Stream', value : 'video_streaming'},
{ text : 'Download Video' , value : 'video_download'},
{ text : 'Share Video' , value : 'videoshare_default'}
]
},
{
app_name : 'Messenger',
app_default : 'message',
options : [
{ text : 'Messaging', value : 'message'},
{ text : 'Voice Calls' , value : 'voice_calls'},
{ text : 'Video Calls' , value : 'video_calls'},
{ text : 'Media Sharing' , value : 'file_transfer'}
]
}
],
selected : []
},
methods: {
selectDefault: function(data,$event){
this.selected[data.app_name].push(data.videoshare_default)
}
}
})
HTML
<div id="app">
<b-col v-for="(data , index) in application" v-bind:key="index" class="p-2" cols="5">
<b-form-group :label="data.app_name" label-class="font-weight-bold">
<b-form-checkbox-group
#input="selectDefault(data,$event)"
v-model="selected[data.app_name]"
:options="data.options"
name="application[]"
stacked
></b-form-checkbox-group>
</b-form-group>
</b-col>
</div>
a FIDDLE:
https://jsfiddle.net/tomexsans/194m0jdq/1/
or is there any other way to do this than what i am doing.
Your selected property is an array, but you want to use key value pairs, which is why you need to make it an object instead, which will store an array of each application type.
To make sure that Vue stays reactive, you need to use the Vue.set or this.$set method to add a property to an object, if that property DOESN'T already exist in that object.
The $event on b-form-checkbox-group returns the entire array of selected values, which we don't want. That's why i use the .native modifier on the event, so i can access the clicked checkbox and it's value.
new Vue({
el: "#app",
data: {
application: [{
app_name: 'Netflix',
app_default: 'videoshare_default',
options: [{
text: 'Video Stream',
value: 'video_streaming'
},
{
text: 'Download Video',
value: 'video_download'
},
{
text: 'Share Video',
value: 'videoshare_default'
}
]
},
{
app_name: 'Messenger',
app_default: 'message',
options: [{
text: 'Messaging',
value: 'message'
},
{
text: 'Voice Calls',
value: 'voice_calls'
},
{
text: 'Video Calls',
value: 'video_calls'
},
{
text: 'Media Sharing',
value: 'file_transfer'
}
]
}
],
selected: {}
},
methods: {
selectDefault(data, event) {
/* Return if the checkbox was unchecked */
if (!event.target.checked) return;
/* Return if the selected value was the default */
if (data.app_default === event.target.value) return;
/* Init the array if it doesn't exist yet.*/
if (!this.selected[data.app_name]) {
this.$set(this.selected, data.app_name, []);
}
const nestedSelected = this.selected[data.app_name];
/* Push in the default value if it doesn't exist alreayd */
if (!nestedSelected.find(value => value === data.app_default)) {
this.selected[data.app_name].push(data.app_default)
}
}
}
})
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.3.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.3.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-col v-for="(data , index) in application" v-bind:key="index" class="p-2" cols="5">
<b-form-group :label="data.app_name" label-class="font-weight-bold">
<b-form-checkbox-group
v-model="selected[data.app_name]"
#input.native="selectDefault(data, $event)"
:options="data.options"
name="application[]"
stacked
></b-form-checkbox-group>
</b-form-group>
</b-col>
{{ selected }}
</div>

Bootstrap-vue: how can I display the text of a selected item?

I am using Bootstrap Vue to render a select input. Everything is working great - I'm able to get the value and change the UI based on the option that was selected.
I am trying to change the headline text on my page - to be the text of the selected option. I am using an array of objects to render the options in my select input.
Here is what I'm using for my template:
<b-form-group
id="mySelect"
description="Make a choice."
label="Choose an option"
label-for="mySelect">
<b-form-select id="mySelect"
#change="handleChange($event)"
v-model="form.option"
:options="options"/>
</b-form-group>
Here is what my data/options look like that I'm passing to the template:
...
data: () => ({
form: {
option: '',
}
options: [
{text: 'Select', value: null},
{
text: 'Option One',
value: 'optionOne',
foo: {...}
},
{
text: 'Option Two',
value: 'optionTwo',
foo: {...}
},
}),
methods: {
handleChange: (event) => {
console.log('handleChange called');
console.log('event: ', event); // optionOne or optionTwo
},
},
...
I can get optionOne or optionTwo, what I'd like to get is Option One or Option Two (the text value) instead of the value value. Is there a way to do that without creating an additional array or something to map the selected option? I've also tried binding to the actual options object, but haven't had much luck yet that route either. Thank you for any suggestions!
Solution
Thanks to #Vuco, here's what I ended up with. Bootstrap Vue passes all of the select options in via :options. I was struggling to see how to access the complete object that was selected; not just the value.
Template:
<h1>{{ selectedOption }}</h1>
<b-form-group
id="mySelect"
description="Make a choice."
label="Choose an option"
label-for="mySelect">
<b-form-select id="mySelect"
v-model="form.option"
:options="options"/>
</b-form-group>
JS:
...
computed: {
selectedOption: function() {
const report = this.options.find(option => option.value === this.form.option);
return option.text; // Option One
},
methods: {
//
}
...
Now, when I select something the text value shows on my template.
I don't know Vue bootstrap select and its events and logic, but you can create a simple computed property that returns the info by the current form.option value :
let app = new Vue({
el: "#app",
data: {
form: {
option: null,
},
options: [{
text: 'Select',
value: null
},
{
text: 'Option One',
value: 'optionOne'
},
{
text: 'Option Two',
value: 'optionTwo'
}
]
},
computed: {
currentValue() {
return this.options.find(option => option.value === this.form.option)
}
}
});
<div id="app">
<b-form-group id="mySelect" description="Make a choice." label="Choose an option" label-for="mySelect">
<b-form-select id="mySelect" v-model="form.option" :options="options" />
</b-form-group>
<p>{{ currentValue.text }}</p>
</div>
Here's a working fiddle.
You have an error in your dictionary.
Text is showed as an option.
Value is what receive your variable when option is selected.
Is unneccesary to use computed property in this case.
let app = new Vue({
el: "#app",
data: {
form: {
option: null,
},
options: [{
value: null,
text: 'Select'
},
{
value: 'Option One',
text: 'Option One'
},
{
value: 'Option Two',
text: 'Option Two'
}
]
}
});
Fiddle with corrections
Documentation

In vue2 v-for nested component props aren't updated after element is removed in parent

For my app I'm using two Vue components. One that renders a list of "days" and one that renders for each "day" the list of "locations". So for example "day 1" can have the locations "Berlin", "London", "New York".
Everything gets rendered ok but after removing the "Day 1" from the list of days the view isn't rendered corrected. This is what happens:
The title of the day that was removed is replaced -> Correct
The content of the day that was removed isn't replaced -> Not correct
Vue.component('day-list', {
props: ['days'],
template: '<div><div v-for="(day, index) in dayItems">{{ day.name }} Remove day<location-list :locations="day.locations"></location-list><br/></div></div>',
data: function() {
return {
dayItems: this.days
}
},
methods: {
remove(index) {
this.dayItems.splice(index, 1);
}
}
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: '<div><div v-for="(location, index) in locationItems">{{ location.name }} <a href="#" #click.prevent="remove(index)"</div></div>',
data: function() {
return {
locationItems: this.locations
}
},
methods: {
remove(index) {
this.locationItems.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [
{
name: 'Day 1',
locations: [
{name: 'Berlin'},
{name: 'London'},
{name: 'New York'}
]
},
{
name: 'Day 2',
locations: [
{name: 'Moscow'},
{name: 'Seul'},
{name: 'Paris'}
]
}
]
}
},
methods: {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Please use Vue-devtools if you are not already using it. It shows the problem clearly, as seen in the image below:
As you can see above, your day-list component comprises of all the days you have in the original list, with locations listed out directly. You need one more component in between, call it day-details, which will render the info for a particular day. You may have the location-list inside the day-details.
Here is the updated code which works:
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<day-list :days="days"></day-list>
</div>
Vue.component('day-list', {
props: ['days'],
template: `
<div>
<day-details :day="day" v-for="(day, index) in days">
Remove day
</day-details>
</div>`,
methods: {
remove(index) {
this.days.splice(index, 1);
}
}
});
Vue.component('day-details', {
props: ['day'],
template: `
<div>
{{ day.name }}
<slot></slot>
<location-list :locations="day.locations"></location-list>
<br/>
</div>`
});
Vue.component('location-list', {
props: ['locations', 'services'],
template: `
<div>
<div v-for="(location, index) in locations">
{{ location.name }}
[x]
</div>
</div>
`,
methods: {
remove(index) {
this.locations.splice(index, 1);
}
}
});
const app = window.app = new Vue({
el: '#app',
data: function() {
return {
days: [{
name: 'Day 1',
locations: [{
name: 'Berlin'
}, {
name: 'London'
}, {
name: 'New York'
}]
}, {
name: 'Day 2',
locations: [{
name: 'Moscow'
}, {
name: 'Seul'
}, {
name: 'Paris'
}]
}]
}
},
methods: {}
});
One other thing - your template for location-list has an error - you are not closing the <a> element. You may use backtick operator to have multi-line templates as seen in the example above, to avoid template errors.
Also you are not supposed to change objects that are passed via props. It works here because you are passing objects which are passed by reference. But a string object getting modified in child component will result in this error:
[Vue warn]: Avoid mutating a prop directly...
If you ever get this error, you may use event mechanism as explained in the answer for this question: Delete a Vue child component