A few days ago I started using vue.js and trying to get the hang of it.
I've been fiddling quite a bit to get this easy example to work: reading the value of selected checkboxes in components with vue.js .
Please see my example on http://jsbin.com/gukoqo/edit?html,js,output
How can I let selected in the parent instance contain the selected values of the checkbox? E.g., filter_a and filter_c are selected, then selected should contain an array: ['filter_a', 'filter_c']
I expected vue.js to make this very easy, but don't know yet how to. Anyone? :)
I'm using the latest vue.js (2.3.3 at the moment)
One possible way.
Vue.component('facet-filter', {
props: ['filter', 'checked'],
template: `<div>
<label class="form-check-label">
<input #change="$emit('change', filter.text, $event)"
class="form-check-input"
type="checkbox"
:value="filter.text"
:checked="checked"
name="filters"> {{filter.text}}
{{$props | json 2}}</label>
</div>`,
});
new Vue({
el: '#app',
data: {
filterFacets: [
{ id: 0, text: 'filter_a' },
{ id: 1, text: 'filter_b' },
{ id: 2, text: 'filter_c' },
{ id: 3, text: 'filter_d' },
],
selected: [], // How can I let this contain ['filter_a', 'filter_b'] etc. when selected?
},
methods:{
onChange(filter, $event){
if ($event.target.checked)
this.selected.push(filter)
else {
const index = this.selected.findIndex(f => f === filter)
if (index >= 0)
this.selected.splice(index, 1)
}
}
}
});
And change your template to
<div id="app">
<facet-filter
v-for="item in filterFacets"
v-bind:filter="item"
v-bind:checked="selected.includes(item.text)"
:key="item.id"
#change="onChange"
>
</facet-filter>
<p><pre>data: {{$data | json 2}}</pre></p>
</div>
Updated bin.
Related
Is it possible to access an element within the nested v-for loop by using the refs index of the element? I mean, I'm trying to focus a textbox that is within the nested v-for loop which I used to access by its refs index. It works fine for a single v-for loop but not with nested.
For more details here's my loop structure:
This works
<div v-for="(comItem, index) in commentItems" :key="comItem.commentId">
<textarea ref="addRep" ></textarea>
</div>
this.$nextTick(() => {
this.$refs.addRep[index].focus()
});
This won't work
<div v-for="(cont, i) in contentItems" :key="cont.contentId">
...
<div v-for="(comItem, index) in commentItems" :key="comItem.commentId">
<textarea ref="addRep" ></textarea>
</div>
</div>
this.$nextTick(() => {
this.$refs.addRep[index].focus()
});
Or
this.$nextTick(() => {
this.$refs.addRep[i].focus()
});
With the nested html v-for loop structure. The focus will just jump around anywhere. To anyone who encountered this kind of scenario. Please assist me if you know the solutions. Thanks.
Trying to calculate the appropriate index within addRep is a little tricky. You'd need the values of both i and index and then count up through the relevant arrays to work out the appropriate index.
A simpler way to do this is to use a dynamic ref name. We still need i and index to find the relevant element but there's no calculation required.
The core trick here is to set the ref to :ref="`addRep${i}`", or equivalently :ref="'addRep' + i" if you prefer. So you'll end up with multiple named refs, addRep0, addRep1, etc., each with its own array of elements. The value of i tells you the ref name and the index tells you the index within that array.
Here's an example:
new Vue({
el: '#app',
data () {
return {
contentItems: [
{
contentId: 1,
comments: [
{
commentId: 1,
text: 'A'
}, {
commentId: 2,
text: 'B'
}, {
commentId: 3,
text: 'C'
}
]
}, {
contentId: 2,
comments: [
{
commentId: 1,
text: 'D'
}
]
}, {
contentId: 3,
comments: [
{
commentId: 1,
text: 'E'
}, {
commentId: 2,
text: 'F'
}
]
}
]
}
},
methods: {
onButtonClick (i, index) {
this.$refs[`addRep${i}`][index].focus()
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<div v-for="(cont, i) in contentItems" :key="cont.contentId">
<h4>{{ cont.contentId }}</h4>
<div v-for="(comItem, index) in cont.comments" :key="comItem.commentId">
<textarea :ref="`addRep${i}`" v-model="comItem.text"></textarea>
<button #click="onButtonClick(i, index)">Focus</button>
</div>
</div>
</div>
I have created a fiddle to explain what I want : https://jsfiddle.net/silentway/aro5kq7u/3/
The standalone code is as follows :
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="mainApp" class="container-fluid">
<p>This is my main list.</p>
<div class="main-list" v-for="(q, index) in questions">
<input type="checkbox"
v-bind:id="'question-' + index"
v-bind:value="{id: index, property: false}"
v-model="answers">
<label v-bind:for="q">{{q}}</label>
</div>
<p>And this is the list of the selected elements in the previous list.</p>
<ul class="selected-list" v-for="(a, index) in answers" :key="a.id">
<li>{{questions[a.id]}} <input type="checkbox"
v-bind:id="'answer-' + index"
v-bind:value="true"
v-model="a.property">
</li>
</ul>
<p>Here's the answer's array for debugging: {{answers}}</p>
</div>
<script>
var mainApp = new Vue({
el: '#mainApp',
data: {
questions: [
"Who are you ?",
"Who, who?",
"You know my name?",
"Look up my number"
],
answers: []
}
});
</script>
I want to display a first list of questions, each with a checkbox. The selected questions are stored in an array called "answers".
From these selected answers I then make another list. Each item has a new corresponding checkbox, for a certain property (which can be true or false). I would like this associated property to be stored in the same array ("answers") as the results from the input in the first list.
What happens with my code is that checking a box in the second list does change the shared array of data ("answers"), but in doing so it also unchecks the corresponding answer in the first list.
Any help would be much appreciated.
I'm having a very hard time following your wording but I gave it a shot anyway. I think you'd be better off keeping selected questions and selected answers in their own array and use a computed property to join them basically. Here's a quick fiddle of it: https://jsfiddle.net/crswll/d8e1g750/21/
new Vue({
data: {
questions: [{
id: 1,
question: 'What the heck 1?'
},
{
id: 2,
question: 'What the heck 2?'
},
{
id: 3,
question: 'What the heck 3?'
},
{
id: 4,
question: 'What the heck 4?'
},
{
id: 5,
question: 'What the heck 5?'
},
],
selectedQuestions: [],
selectedAnswers: [],
},
computed: {
answers() {
return this.selectedQuestions.map(id =>
this.questions.find(question => question.id === id)
)
},
selectedAnswersSimpleList() {
return this.selectedAnswers
.map(id => this.questions.find(question => question.id === id))
.map(question => question.question)
}
},
}).$mount('#app')
I get an multi dimensional array named response from back-end and what I'm trying to do is to create several text-fields depending on the number of element I have in response (each response element has some inner elements like response[0][0] and response[0][1] that each of them is a object contains caption,name,etc for each text-field. for example response[0][0].name get name of response[0][0] element).
What I want is to bind these text-fields to an other two dimensional array named data so I can get value of them and use them as I want.
Here's the code:
<v-layout row wrap v-for="(row,i) in response" :key = "i">
<v-layout v-for="(col,j) in row" :key = "j">
<v-text-field
:name = "col.name"
:label = "col.caption"
v-model="data[i][j]"//I think somehow i should create data[i][j] element
first,like data[i] =[]
>
</v-text-field>
</v-layout>
</v-layout>
And script is :
data () {
return {
data: [],
response: []
}
},
mounted: function () {
//get response from back-end
}
I'm new to Vue and javascript, any help would be appreciate...
please comment If it's not clear.
should work... can you clarify what the issue is? is it matter of generating the response array?
new Vue({
el: '#app',
data: {
data: [
[{
name: 'name00',
caption: 'caption00'
},
{
name: 'name01',
caption: 'caption01'
}
],
[{
name: 'name10',
caption: 'caption10'
},
{
name: 'name11',
caption: 'caption11'
}
]
],
response: [
['',''],
['','']
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.5/vue.min.js"></script>
<div id="app">
<div v-for="(i, ii) in data" :key="ii">
<div v-for="(j, jj) in i" :key="jj">
{{j.name}}
<input v-model="response[ii][jj]" />
</div>
</div>
<pre>{{response}}</pre>
</div>
Vue JS computed property is not triggered With this markup
<!-- language: lang-html -->
<p>£{{plant_price}}</p>
<div v-if="selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
<!-- language: lang-js -->
var app = new Vue({
el: '#vueApp',
data: {
selected: {
type: {a: '' , b: ''},
vehicle: '',
plant: {
}
},
computed: {
plant_price: function() {
if (this.selected.plant.variations.length > 0 ) {
var variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
...
selected.plant is populated by clicking on a plant - triggering the updateSelected method.
<div class="col-sm-4" v-for="(plant, i) in step2.plants">
<div v-on:click="updateSelected(plant)" ....
methods: {
updateSelected: function(plant) {
this.selected.plant = plant; // selected plant
if (this.selected.plant.variations.length > 0 ) {
this.selected.plant.selected_variation = this.selected.plant.variations[0].id; // set the selected ID to the 1st variation
I have checked through the debugger, and can see that all the correct properties are available.
selected:Object
type:Object
vehicle: "Truck"
plant:Object
id:26
price:"52"
regular_price:"100"
selected_variation:421
variations:Array[2]
0:Object
id:420
name:"small"
price:52000
regular_price:52000
1:Object
etc...
I have a computed property, which should update the plant_price based on the value of selected.plant.selected_variation.
I grab selected.plant.selected_variation and search through the variations to retrieve the price. If no variation exists, then the plant price is given.
I have a method on each product to update the selected plant. Clicking the product populates the selected.plant and triggers the computed plant_price to update the price (as the value of selected.plant.selected_variation has changed).
However, the computed plant_price is not triggered by the select. Selecting a new variant does what its supposed to, it updates selected.plant.selected_variation. Yet my plant_price doesn't seem to be triggered by it.
So I refactored my code by un-nesting selected.plant.selected_variation. I now hang it off the data object as
data = {
selected_variation: ''
}
and alter my computer property to reference the data as this.selected_variation. My computed property now works??? This makes no sense to me?
selected.plant.selected_variation isn't reactive and VM doesn't see any changes you make to it, because you set it after the VM is already created.
You can make it reactive with Vue.set()
When your AJAX is finished, call
Vue.set(selected, 'plant', {Plant Object})
There're two ways you can do it, what you are dealing with is a nested object, so if you want to notify the changes of selected to the others you have to use
this.$set(this.selected, 'plant', 'AJAX_RESULT')
In the snippet I used a setTimeout in the created method to simulate the Ajax call.
Another way you can do it is instead of making plant_price as a computed property, you can watch the changes of the nested properties
of selected in the watcher, and then update plant_price in the handler, you can check out plant_price_from_watch in the snippet.
Vue.component('v-select', VueSelect.VueSelect);
const app = new Vue({
el: '#app',
data: {
plant_price_from_watch: 'not available',
selected: {
type: {a: '' , b: ''},
vehicle: "Truck"
}
},
computed: {
plant_price() {
return this.setPlantPrice();
}
},
watch: {
selected: {
handler() {
console.log('changed');
this.plant_price_from_watch = this.setPlantPrice();
},
deep: true
}
},
created() {
setTimeout(() => {
this.$set(this.selected, 'plant', {
id: 26,
price: '52',
regular_price: '100',
selected_variation: 421,
variations: [
{
id: 420,
name: "small",
price: 52000,
regular_price: 52000
},
{
id: 421,
name: "smallvvsvsfv",
price: 22000,
regular_price: 22000
}
]
})
}, 3000);
},
methods: {
setPlantPrice() {
if (!this.selected.plant) {
return 'not available'
}
if (this.selected.plant.variations.length > 0 ) {
const variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<p>£{{plant_price}}</p>
<p>£{{plant_price_from_watch}}</p>
<div v-if="selected.plant && selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
</div>
I've got a form with about 10 select elements built from an array in my Vue data.
The array of selectors is empty initially and then an AJAX call populates the array and Vue builds the HTML - I've kept the snippet below simplified just to demonstrate the issue I'm having with v-model
I want to create an object that has all the selected values in it, so I'm trying to use v-model="selected[ selector.name ]" as per the example below.
I want to easily be able to ask for selected.make or selected.fuel
Now this works if I initialize the selected property like this:
selected: { make: 'audi', fuel: 'petrol' }
If I leave it blank, like in the example, {}, then it doesn't get updated.
I don't want to manually hardcode all the properties of selected, I only want to be listing them once in the server side code that gets sent via AJAX
So am I missing something completely obvious, should I be doing this in a different way?
Maybe a method to find the dropdown that matches a field name and returns the value? Just that doesn't seem like a very Vue thing to do.
var app = new Vue({
el: '#example',
data: {
selectors: [
{
name: 'make',
options: ['audi','bmw']
},
{
name: 'fuel',
options: ['petrol','diesel']
}
],
selected: {}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="example">
<template v-for="selector in selectors">
<select v-model="selected[ selector.name ]">
<option v-for="option in selector.options">{{option}}</option>
</select>
</template>
<p>
{{selected.make}}
<br />
{{selected.fuel}}
</p>
</div>
it's probably becuase you're not setting new keys on an object with this.$set
try:
this.$set(this.selected, 'make', 'audi')
Not using this.$set - alias of Vue.set - will mean Vue doesn't set the new key as reactive, and in turn won't be watching for any updates to it, docs: https://v2.vuejs.org/v2/api/#vm-set
var app = new Vue({
el: '#example',
data: {
selectors: [{
name: 'make',
options: ['audi', 'bmw']
}, {
name: 'fuel',
options: ['petrol', 'diesel']
}],
selected: null,
},
created () {
// this would happen following your ajax request - but as an example this should suffice
this.selected = {}
this.selectors
.forEach((selector) => {
this.$set(this.selected, selector.name, '')
})
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="example">
<div v-if="selected">
<select v-model="selected[selector.name]" v-for="selector in selectors">
<option :value="option" v-for="option in selector.options">
{{option}}
</option>
</select>
<p>make: {{selected.make}}<p>
<p>fuel: {{selected.fuel}}</p>
<pre>{{ selected }}</pre>
</div>
</div>