Function parameter as this.hint[variableA].variableB - vue.js

I'm using Vue.js and would like to
I want to set hint value by variable like this.hint[variableA].variableB = 1
How could I do it
data: {
hint: [{a:0,b:0,c:0},{a:0,b:0,c:0},{a:0,b:0,c:0}],
}
methods: {
selectHint(){
this.setHint(0,a)
},
setHint(i,j){
this.hint[i].j = 1
},
},

Here, hint is an array of object. To acces one of it's child, you need to access the object at the index i.
Then, if you want to add a new variable, if you're doing it like this.hint[i].newVariable = 1 where newVariable isn't already in the object, it won't be reative.
If you want to set reactivity (refresh the dom when this variable is added or change in the future), you can do it by using the Vue.set() method (see this link for more informations)
Example
here when you press the button, il will add a j property to the first hint with the value 1
new Vue({
el: '#app',
data: {
hint: [{
a: 0,
b: 0,
c: 0
}, {
a: 0,
b: 0,
c: 0
}, {
a: 0,
b: 0,
c: 0
}],
selectedHint: 0,
text: "",
},
methods: {
setHint() {
Vue.set(this.hint[this.selectedHint], this.text, 1)
this.text = ""
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(h, index) of hint">
<p :key="index">{{JSON.stringify(h, null, 4)}}</p>
</template>
<input type="text" label="property" v-model="text" />
<select v-model="selectedHint" #change="changeSelectedIndex">
<template v-for="(h, index) of hint">
<option :key="index" :value="index">{{index}} </option>
</template>
</select>
<button #click="setHint">Add </button>
</div>

It can be done with this.hints[i][j]=1, but that will not be reactive, so you should use this.$set helper :
this.$set(this.hints, i ,{ [j] : 1,...this.hints[i]})

Related

Vue2 JS - v-for component in a form

i'm facing this problem.
I have a form where i get a component with a v-for cycle.
In this component I have a multiple select.
I cannot understand how to gather data from the components when submitting the form
I modified the question thanks to #Reinier68 tip on Components, bu still not getting
This is my component
<template>
<div>
<select
v-bind:value="selectedValue"
v-on:select="$emit('select', $event.target.selectedValue)"
:options="options"></select>
</div>
</template>
<script>
export default {
name: 'categorySelect',
data: function(){
return {
selected: []
}
},
props: {
options: Array,
selectedValue: null
},
}
</script>
Then, in the parent, i use this component in a v-for
<div v-for="cu in s.cus" :key="cu.cuId">
<category-select :options="options" v-model="selected[cu.cuId]" :selectedValue="value"></category-select>
</div>
The problem is that when submitting the for i still get nothing, my selected[] is null
props are for input, $emit is for output.
You pass in the value into the component with the prop, then on change, you use $emit to send the value out, then listen for the value on the component.
To assign back the selected value it's better to use a method but can be achived inline #select="v => $set(values, index, v)" and because its an array you need to use $set else it wont be reactive like #select="v => values[index] = v"
Below is an example, not using an object as you have not provided it, but; it would be the same prop in, emit out.
const categorySelect = {
template: '#category-select',
props: ['selected', 'options'],
data() {
return {
value: this.selected
}
},
methods: {
onChange(event) {
this.$emit('select', event.target.value)
}
}
}
//
new Vue({
el: '#app',
components: {
categorySelect
},
data() {
return {
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
values: [1, 0, 5, 0]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<div id="app">
<!-- -->
<div v-for="(value, index) in values" :key="index">
<category-select
:options="options"
:selected="value"
#select="v => $set(values, index, parseInt(v, 10))"
/>
</div>
<!-- -->
<pre>{{ values }}</pre>
</div>
<template id="category-select">
<select
v-model="value"
#change="onChange">
<option v-for="option in options">{{option}}</option>
</select>
</template>

VueJS: How to create v-model value inside object array

I have several input fields like so:
<input type=text" v-model="InputVModel1">
<input type=text" v-model="InputVModel2">
and I want to place these v-model values inside an array of objects, like so:
array = [{id:1,value:InputVModel1},{id:2,value:InputVModel2},..]
What's the best way to achieve this?
Is it to use $set in a computed value to 'push' these into the array like:
computed: {
computed_array: function(){
object= {"id":1,"value":InputVModel1}
this.$set(this.array, object) //push them with a for loop
[..]
}
}
Or is there a more elegant way to do this?
Background: I want to use the final array for 'vuedraggable' to change the order of the objects while maintaining other important meta infos for each value.
new Vue({
el: '#app',
data: {
list: [
{ id: 1, value: 'foo' },
{ id: 2, value: 'bar' },
{ id: 3, value: 'baz' }
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div
v-for="(record, i) in list"
:key="i"
>
{{ record.id }}
<input v-model="record.value"/>
{{ record.value }} <!-- To check if value key is updating in the list -->
</div>
</div>
PS: Use list (final array) with for vue-draggable.

How do i get the v-model values of a v-for components if i iterate with only numbers?

I have v-for form-group components i iterated over a select's value(integer). I want to get the values of the iterated v-models, but i just can't seem to get it right
TEMPLATE
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
SCRIPT DATA
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
}
}
I've tried changing the model to low[n] or declaring low in data as object {} but either of these seems to be undefined according to TypeErrors i've encoutered.
How am i suppose to get the low[n]'s values?
EDIT:
Here is the full code:
<template>
<div>
<b-form #submit="onSubmit" #reset="onReset">
<b-form-group
id="input-group-1"
label="Jumlah Lowongan:"
label-for="selectJumlahLow"
description="Silahkan pilih satu."
v-if="show"
>
<b-form-select id="selectJumlahLow" v-model="form.jumlahlow" :options="selow" required></b-form-select>
</b-form-group>
<b-form-group label="Nama Lowongan:" v-for="n in parseInt(form.jumlahlow)" :key="n">
<b-form-input required placeholder="Masukkan nama lowongan" v-model="low"></b-form-input>
</b-form-group>
<b-button type="submit" variant="primary">
{{ buttonText }}
<i class="material-icons">arrow_forward_ios</i>
</b-button>
<b-button type="reset" variant="danger">Reset</b-button>
</b-form>
<b-card class="mt-3" header="Form Data Result">
<pre class="m-0">{{ form }}</pre>
</b-card>
</div>
</template>
<script>
export default {
name: "lowonganForm",
data() {
return {
form: {
jumlahlow: 1,
checked: [],
low: []
},
selow: [
{ text: "Pilih Satu", value: null, disabled: true },
1,
2,
3,
4,
5,
6
],
show: true,
target: false,
buttonText: "Next"
};
},
methods: {
onSubmit(evt) {
evt.preventDefault();
alert(JSON.stringify(this.form));
// if (this.jumlahlow !== null || !this.jumlahlow < 1) {
// this.show = false;
// }
},
onReset(evt) {
evt.preventDefault();
// Reset our form values
this.form.jumlahlow = null;
this.form.checked = [];
// Trick to reset/clear native browser form validation state
this.show = false;
this.$nextTick(() => {
this.show = true;
});
}
},
computed: {}
};
</script>
You should try to model your data for how you want the view to be rendered. If you want to have a list of input boxes, then the data for those inputs should be defined in an array that is prepopulated with those items, or when you need to adjust the number of items you should add those data items to the array. You'll avoid reactivity problems this way too.
Here's an example of what I mean:
new Vue({
el: '#app',
data: {
maxCount: 5,
count: 3,
items: [],
data: '',
},
computed: {
visibleItems() {
return this.items.slice(0, this.count)
}
},
created() {
// Define the data upfront so it will be reactive
for (let i = 0; i < this.maxCount; i++) {
this.items.push({
firstName: '',
lastName: '',
})
}
},
methods: {
submit() {
// Transform the data into some JSON that is
// compatible with your API
const data = this.visibleItems.map(item => ({
first_name: item.firstName,
last_name: item.lastName,
role: 'user',
}))
this.data = JSON.stringify(data, null, ' ')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
Number of people:
<select v-model="count">
<option v-for="i of maxCount" :value="i">{{ i }}</option>
</select>
</div>
<div v-for="item of visibleItems">
<input placeholder="First name" v-model="item.firstName">
<input placeholder="Last name" v-model="item.lastName">
</div>
<button #click="submit">Submit</button>
<pre>{{ data }}</pre>
</div>
Try this example.
<div id="app">
<div>
<select v-model="jumlahlow">
<option v-for="i in selects" :key="i">{{ i }}</option>
</select>
</div>
<div v-for="num, index in parseInt(jumlahlow)">
<input v-model="lows[index].value" />
</div>
</div>
And JS
new Vue({
el: '#app',
data: {
lows: [
{
value: ''
}
],
jumlahlow: 1,
selects: [
1,
2,
3,
4,
5,
6
]
},
watch: {
jumlahlow: function (val) {
this.lowsTmp = this.lows;
this.lows = [];
for (let i = 0; i < val; i++) {
const currentVal = typeof this.lowsTmp[i] !== 'undefined' ? this.lowsTmp[i].value : '';
this.addLow(currentVal);
}
}
},
methods: {
addLow: function(val) {
this.lows.push({ value: val });
}
}
})
Directly check here: https://jsfiddle.net/abinhho/m3c8r4tj/2/
you are iterating v-for="n in parseInt(form.jumlahlow)" but that's an Object and v-for works on array not on objects.
Here you can use array of objects to iterate, for example:-
form: [{
jumlahlow: 1,
checked: [],
low: []
}]
and after that you will have to write v-for="n in form" then try accessing low by form.low

Incorrect order of Vue components after copy operation

I have several accordions (every one is a single Vue component) and they are expanded by default. There's also a 'copy' function allowing to make a duplicate of every component.
Vue.component("Accordion", {
template: "#accordion-template",
data: function () {
return {
open: true
}
},
methods: {
toggle: function () {
this.open = !this.open;
}
}
});
new Vue({
el: '#vue-root',
data: {
devices: [
{
name: "a", description: "first"
},
{
name: "b", description: "second"
},
{
name: "c", description: "third"
}
]
},
methods: {
copy: function (device) {
var index = this.devices.indexOf(device) + 1;
var copy = {
name: device.name + "_copy",
description: device.description + "_copy"
};
this.devices.splice(index, 0, copy);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="vue-root">
<div class="device" v-for="device in devices">
<accordion>
<div slot="acc-head">
<span>{{ device.name }}</span><br/>
<button #click="copy(device)">copy</button>
</div>
<div slot="acc-body">
{{ device.description }}
</div>
</accordion>
</div>
</div>
<script type="text/x-template" id="accordion-template">
<div>
<slot name="acc-head"></slot>
<button #click="toggle">Open: {{ open }}</button>
<div :class="open ? 'active' : 'hidden'">
<slot name="acc-body"></slot>
</div>
</div>
</script>
When all accordions are collapsed (in other words 'open: false') and I try to duplicate an accordion from the middle of list (for example b), I expect appearing of the new component named 'name'_copy and it must be expanded by default. But instead of this, the new component has the same values of all attributes as the duplicated one and the last component in the list becomes expanded.
How can I solve this issue?
Fiddle: https://jsfiddle.net/j3ydt1m7/
Short answer
Add a key in your v-for loop: v-for="device in devices" :key="{something here}". Your key must be unique and identify each device, even after device copy
Code
Please check: https://jsfiddle.net/Al_un/9cradxvp/. For debugging purpose, I changed few things:
I put device as props of <accordion> so that I can use device properties in console.log
Copying device is now emitted from <accordion>. Vue doc on listening to child component events
I have added mounted() and updated() hooks. More about Lifecycle hooks
Each device has an ID
Long answer
About list rendering
If key is not provided in v-for loop, Vue does not know how to update a List. From Vue documentation:
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item.
Let's consider your list (I have added one element)
[
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
]
Now, let's copy node "b". Without :key="device.id", the console output is
4: d is mounted
3: c is updated
5: b_copy is updated
With :key="device.id", the console output is only:
5: b_copy is mounted
Basically, without keys, there are:
two updates: c becomes b_copy, d becomes c
one insert: d is created
Consequently, the last element is recreated every time you proceed to a copy. As open default value is true, obviously, d has open = true.
If each element has a :key="device.id", then only element b_copy is created
To check that, remove the :key="device.id" from my fiddle and see what happens in the console
Selecting a key
As the key must uniquely identify your device, you should not use index as a key as device index in the array changes whenever you copy a device
Additionally, an ID field is preferred because there is no guarantee that your devices names are unique. What if you initialise the list with
[
{ name: "a"},
{ name: "b"},
{ name: "a"}
]
From a functional point of view, this is correct.
When working with Vue and lists you should add a key prop to the element with v-for. Using the key like this, let's Vue know that you mean a specific element.
<div class="device" v-for="device in devices" :key="device.name">
I believe the reason for this is that due to performance reasons Vue by default adds a new element as the last element and then updates the data in the other nodes. Thus, the new element that you add is actually the last one in the list which has open set as true.
A little addition to your code:
Instead of providing the "device" object and searching for its index you could just pass the index directly.
This is what i mean: jsFiddle
Vue.component("Accordion", {
template: "#accordion-template",
data: function() {
return {
open: true
}
},
methods: {
toggle: function() {
this.open = !this.open;
}
}
});
new Vue({
el: '#vue-root',
data: {
devices: [{
name: "a",
description: "first"
},
{
name: "b",
description: "second"
},
{
name: "c",
description: "third"
},
]
},
methods: {
copy: function(index) {
var device = this.devices[index];
var copy = {
name: device.name + "_copy",
description: device.description + "_copy"
};
this.devices.splice(index + 1, 0, copy);
},
remove: function(index) {
this.devices.splice(index, 1);
}
}
});
.device {
margin: 10px 0;
}
.active {
display: block;
}
.hidden {
display: none;
}
div.device {
border: 1px solid #000000;
box-shadow: 1px 1px 2px 1px #a3a3a3;
width: 300px;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-root">
<div class="device" v-for="(device, index) in devices" :key="device.name">
<accordion>
<div slot="acc-head">
<span>{{ device.name }}</span><br/>
<button #click="copy(index)">copy</button>
<button #click="remove(index)">remove</button>
</div>
<div slot="acc-body">
{{ device.description }}
</div>
</accordion>
</div>
</div>
<script type="text/x-template" id="accordion-template">
<div>
<slot name="acc-head"></slot>
<button #click="toggle">Open: {{ open }}</button>
<div :class="open ? 'active' : 'hidden'">
<slot name="acc-body"></slot>
</div>
</div>
</script>

JSON and v-if troubles with Vue.js

In the following example neither of the v-if related divs seem to get rendered before or after clicking the Add button. It seems like Vue.js isn't running any updates when the pizzas JSON object is updated.
Is there a solution to this problem without resorting to changing the pizzas variable into being an array?
<div id="app">
<div v-for="pizza in pizzas">
{{ pizza }}
</div>
<div v-if="totalPizzas === 0">
No pizza. :(
</div>
<div v-if="totalPizzas > 0">
Finally, some pizza! :D
</div>
<button #click="add">Add</button>
</div>
var app = new Vue({
el: '#app',
data: {
pizzas: {}
},
methods: {
add: function() {
this.pizzas['pepperoni'] = { size: 16, toppings: [ 'pepperoni', 'cheese' ] };
this.pizzas['meaty madness'] = { size: 14, toppings: [ 'meatballs', 'sausage', 'cajun chicken', 'pepperoni' ] };
},
totalPizzas: function() {
return Object.keys(this.pizzas).length;
}
}
});
There are several things to be improved in your code. Most of them are about syntax. For example, methods should be called, but computed properties can be queried directly: that's why it's #click="add()", but totalPizzas === 0 makes sense only if it's a computed property.
The crucial thing to understand, however, is how reactivity works in VueJS. See, while you change your object innards, adding new properties to it, this change is not detected by VueJS. Quoting the docs:
Vue does not allow dynamically adding new root-level reactive
properties to an already created instance. However, it’s possible to
add reactive properties to a nested object using the Vue.set(object, key, value) method:
Vue.set(vm.someObject, 'b', 2)
You can also use the vm.$set instance method, which is an alias to the
global Vue.set:
this.$set(this.someObject, 'b', 2)
Sometimes you may want to assign a number of properties to an existing
object, for example using Object.assign() or _.extend(). However, new
properties added to the object will not trigger changes. In such
cases, create a fresh object with properties from both the original
object and the mixin object:
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
And this is how it might work:
var app = new Vue({
el: '#app',
data: {
pizzas: {}
},
computed: {
totalPizzas: function() {
return Object.keys(this.pizzas).length;
}
},
methods: {
add: function() {
this.pizzas = Object.assign({}, this.pizzas, {
pepperoni: { size: 16, toppings: [ 'pepperoni', 'cheese' ] },
['meaty madness']: { size: 14, toppings: [ 'meatballs', 'sausage', 'cajun chicken', 'pepperoni' ] }
});
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<div id="app">
<div v-for="pizza in pizzas">
Size: {{ pizza.size }} inches
Toppings: {{ pizza.toppings.join(' and ') }}
</div>
<div v-if="totalPizzas === 0">
No pizza. :(
</div>
<div v-if="totalPizzas > 0">
Finally, some pizza! :D
</div>
<button #click="add()">Add</button>
</div>