How can i access and create a nested object with v-model on vue? - vuejs2

I'm new to vuejs and I have a problem with v-model, I want to know if has some way to access a nested object and auto-create with v-model
my html:
<div class="table-values" v-for="month in months">
<div class="month" v-text="month"></div>
<div>
<input type="text" class="form-control" inputmode="decimal" #blur="test" v-model="consumption[month].rush_hour">
</div>
<div>
<input type="text" class="form-control" inputmode="decimal" v-model="consumption[month].normal_hour">
</div>
<div>
<input type="text" class="form-control" inputmode="decimal" readonly>
</div>
<div>
<input type="text" class="form-control" inputmode="decimal" v-model="energy[month].rush_hour">
</div>
<div>
<input type="text" class="form-control" inputmode="decimal" v-model="energy[month].normal_hour">
</div>
</div>
My JS:
var app = new Vue({
el: "#app",
data() {
return {
months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
consumption: {},
energy: {},
}
})
I want to create this for example:
{
consumption: {
Janeiro: {
rush_hour: 1,
normal_hour: 2
},
Fevereiro: {
rush_hour: 1,
normal_hour: 2
}
},
energy: {
Janeiro: {
rush_hour: 1,
normal_hour: 2
},
Fevereiro: {
rush_hour: 1,
normal_hour: 2
}
}
I Tested only with v-model="Consumption[month]" and worked, but when i use v-model="Consumption[month]['rush_hour']" or v-model="Consumption[month].rush_hour" v-model (Ex: Consumption.Janeiro.rush_hour) throw an error on console.

If you component is increasing in complexity to come to a point like this, you better extract the logic to another component and receive the dynamic values as props so that you have something like:
props: {
month // "January"
},
data() {
return {
consumption: {
rush_hour: 0,
normal_hour: 0
},
energy: {
rush_hour: 0,
normal_hour: 0
}
}
}
// Somewhere, you do this:
$emit('input', {
consumption: this.consumption,
energy: this.energy,
})

The error occurred because when access model of null (consumption['Janeiro'] = null)
I recommend you init your consumption and energy in your data function.
Here is my code:
data() {
const months = [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro",
];
const consumption = {};
const energy = {};
const initialValue = {
rush_hour: 1,
normal_hour: 2,
};
months.forEach((month) => {
consumption[month] = { ...initialValue };
energy[month] = { ...initialValue };
});
return {
months: months,
consumption: consumption,
energy: energy,
}
}
Here is code sandbox.
Hope this help!

Related

Vue is not update DOM when object property changes in array of objects

I have a const array of objects which set to the data property on created(), I am trying to update the object properties as the user entered the form, the Console print shows the value updated, but the DOM is changing. DOM is updated when the key value is changed too but I do not want to change the key value
Data Array:
const invoices = [
{
number: "BBRFL",
client: "Ellison Daugherty",
amount: 8800,
status: "Paid",
},
{
number: "TYUBK",
client: "Myrna Vinson",
amount: 4097,
status: "Paid",
},
{
number: "IRUBR",
client: "Mcmillan Warner",
amount: 6466,
status: "Draft",
},
];
Here is the app.
const app = new Vue({
el: "#app",
components: {
"create-invoice": CreateInvoice,
"invoice-item": InvoiceItem,
},
data() {
return {
invoices: invoices,
status: null,
};
},
created: function () {
const invoices = localStorage.getItem("invoices");
if (invoices) {
this.invoices = JSON.parse(invoices);
}
},
computed: {
count: function () {
return this.filteredInvoices.length;
},
filteredInvoices: function () {
if (this.status) {
return this.invoices.filter((invoice) => invoice.status == this.status);
}
return this.invoices;
},
},
methods: {
saveInvoice(invoice) {
this.invoices.push(invoice);
},
invoiceUpdate(invoice) {
const index = this.invoices.findIndex((i) => i.number === invoice.number);
// this.invoices.splice(index, 1, invoice);
Vue.set(this.invoices[index], "client", invoice.client);
Vue.set(this.invoices[index], "amount", invoice.amount);
Vue.set(this.invoices[index], "status", invoice.status);
},
invoiceDelete(number) {
const index = this.invoices.findIndex((i) => i.number === number);
this.invoices.splice(index, 1);
},
},
watch: {
invoices: {
deep: true,
handler: function (invoices) {
localStorage.setItem("invoices", JSON.stringify(invoices));
},
},
},
template: `
<div>
<div class="row">
<div class="col-md-4 text-left">
<h1>Invoices</h1>
<p class="text-dark">There are {{count}} Total Invoices</p>
</div>
<div class="col-md-4 text-start" style="margin-top: 10px">
<label for="status">Filter</label>
<select name="filter" id="status" class="form-control" v-model="status">
<option value="Draft">Draft</option>
<option value="Paid">Paid</option>
<option value="Pending">Pending</option>
</select>
</div>
<div class="col-md-4 text-right" style="margin-top: 10px">
<button class="btn btn-primary mt-4" id="createInvoice">
New Invoice
</button>
</div>
</div>
<div class="col-md-12">
<div class="row mt-2 mb-2">
<div
class="invoice col-md-12"
>
<invoice-item
v-for="(n, index) in filteredInvoices"
:key="n.number"
:initial-client="n.client"
:initial-number="n.number"
:initial-amount="n.amount"
:initial-status="n.status"
#update-invoice="invoiceUpdate"
#delete-invoice="invoiceDelete"
></invoice-item>
</div>
<div class="text-center" v-if="filteredInvoices.length === 0"><p>No invoice found</p></div>
</div>
</div>
<create-invoice #saveInvoice="saveInvoice" ></create-invoice>
</div>
</div>
`,
});
I had tried, this.$set, Vue.set, Direct assigning to property, assingment using splice function, but none of working. It only works with the change of value of key in for loop. Which I do not want to update.
Jsfiddle
Any Help? Thanks in advance.

v-on does not working why not receive $emit in custom component

I'm create a an component which represents my money field.
My target is on add element in list, set zero on money field to add next element in list...
But, my problem is that not working when send using $emit event to clear input to improve usability.
$emit works as described on image bellow
My money field:
<template>
<div class="input-group" #clear="clearInputField()">
<span>{{ title }}</span>
<input ref="displayMoney" type="text" v-model="displayMoney" #focus="isActive = true" #blur="isActive = false" />
</div>
</template>
<script>
export default {
props: {
title: String,
},
data() {
return {
money: 0,
isActive: false,
};
},
methods: {
clearInputField() {
console.log("Its work event");
this.money = 0;
this.displayMoney = "";
},
},
computed: {
displayMoney: {
get: function () {
if (this.isActive) {
return this.money;
} else {
return this.money.toLocaleString("pt-br", { style: "currency", currency: "BRL" });
}
},
set: function (modifiedMoney) {
let newMoney = parseFloat(modifiedMoney.replace(/[^\d.]/g, "."));
if (isNaN(newMoney) || newMoney.length == 0) {
newMoney = 0;
}
this.$emit("input", newMoney);
return (this.money = parseFloat(newMoney));
},
},
},
};
</script>
My principal component
<template>
<div class="wish-list">
<div class="row">
<div class="input-group">
<span>Digite sua meta: </span>
<input ref="descriptionWish" type="text" v-model="descriptionWish" />
</div>
<MoneyField title="Valor (R$): " v-model="valueWish" #keyup.native.enter="addWish" />
<button id="btnCalculate" #click="addWish()">Adicionar a lista de desejos</button>
</div>
<div class="list-items">
<ul>
<li v-for="wish in wishes" :key="wish">{{ wish }}</li>
</ul>
</div>
</div>
</template>
<script>
import MoneyField from "./Fields/MoneyField";
export default {
components: {
MoneyField,
},
data() {
return {
wishes: [],
valueWish: 0,
descriptionWish: "",
};
},
methods: {
addWish() {
if (!isNaN(this.valueWish) && this.valueWish > 0 && this.descriptionWish.length > 0) {
this.wishes.push(
`${this.descriptionWish} => ${this.valueWish.toLocaleString("pt-BR", { currency: "BRl", style: "currency" })}`
);
this.descriptionWish = "";
console.log("addWish");
this.valueWish = 0;
this.$emit("clear");
this.$refs.descriptionWish.focus();
}
this.valueWish = 0;
},
},
};
</script>
I still don't understand much about vueJS, but I believe it's something related to parent and child elements, but I've done numerous and I can't get my answer.
sorry for my bad english .
The emit sends an event from the child to the parent component not as you've done, to run a method from the child component you could add a ref in the component inside the parent one like :
<MoneyField title="Valor (R$): "
ref="moneyField" v-model="valueWish" #keyup.native.enter="addWish" />
then run this.$refs.moneyField.clearInputField() instead this.$emit("clear")

How change inputs type in VUE?

I have a input with type=radio
<div id="test">
<div class="input-group mb-3" v-for="(input, k) in inputs" :key="k">
<input type="radio" class="abc" disabled>
</div>
</div>
How can on click button i can change all inputs with class="abc" from radio to checkbox
<script>
var app = new Vue({
el: "#test",
methods: {
changeInputType(e) {
if (e.target.checked == true) {
alert('true');
} else {
alert('false');
}
},
}
});
</script>
is any simple way to do this in this changeInputType function ?
bind the input type to variable and change that variable
<template>
<div>
<input :type="inputType" />
<button #click="changeType />
</div>
</template>
<script>
export default {
data() {
return {
inputType: "text",
}
},
methods: {
changeType() {
if (this.inputType === "text") {
this.inputType = "radio"
} else {
this.inputType = "text"
}
}
}
}
</script>
Here is the solution of your answer, By clicking on button all input types will be changed from radio to checkbox.
<div id="test">
<div class="input-group mb-3" v-for="(input, k) in inputs" :key="k">
<input :type="input" class="abc" disabled>
</div>
<div class="btn btn-sm" #click="changeInputType">Change Input Type</div>
</div>
<script>
var app = new Vue({
el: "#test",
data() {
return {
inputs: ['radio', 'radio']
}
},
methods: {
changeInputType() {
this.inputs = ['checkbox', 'checkbox']
},
}
});
</script>
On butt click change the type variable to checkbox.
<script>
var app = new Vue({
el: "#test",
data() {
return {
input: 'radio'
}
},
methods: {
btnClick() {
this.input = 'checkbox'
},
}
});
</script>

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

Vue 2 Emit selected data back to parent component

Struggling to sort out how to get a selected value from a Typeahead component to pass back to the parent component. I'm allowing the user to search from a variety of data to link a record to a post. Once the user clicks one of the typeahead drop-down records, I pass the item to the sendlink method - I've checked that the data passes ok. When I do the emit using the selected-link event, I'm not getting the data in the parent component.
PostList.vue
<template>
<div>
<div v-if='posts.length === 0' class="header">There are no posts yet!</div>
<form action="#" #submit.prevent="createPost()" class="publisher bt-1 border-fade bg-white" autocomplete="off">
<div class="input-group">
<input v-model="post.content" type="text" name="content" class="form-control publisher-input" placeholder="What's the lastest?" autofocus>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">Post</button>
</span>
</div>
<span class="publisher-btn file-group">
<i class="fa fa-camera file-browser"></i>
<input type="file">
</span>
</form>
<div #click="doit" v-on:selected-link="onSelectedLink">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3">
</typeahead>
<post v-for="post in posts"
:key="post.id"
:post="post"
#post-deleted="deletePost($event)">
</post>
</div>
</template>
<script>
var axios = require("axios");
import post from './PostItem.vue';
import typeahead from './Typeahead.vue';
export default {
components: {
post,
typeahead
},
props: ['postableId', 'postableType', 'model'],
data: function() {
return {
modellink: {
"name": "n/a",
"description": "",
"id": null,
"model": "n/a"
},
post: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
posts: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchPostsList();
},
methods: {
onSelectedLink: function (talink) {
alert(JSON.stringify(talink, null, 4));
this.link = talink
},
doit() {
alert(JSON.stringify(this.modellink, null, 4));
},
fetchPostsList() {
if( this.postableId ) {
axios.get('/api/' + this.postableType + '/' + this.postableId + '/posts').then((res) => {
this.posts = res.data;
});
} else {
axios.get('/api/post').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.posts = res.data;
});
}
},
createPost() {
axios.post('api/post', {content: this.post.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.post.content = '';
// this.post.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchPostsList();
})
.catch((err) => console.error(err));
},
deletePost(post) {
axios.delete('api/post/' + post.id)
.then((res) => {
this.fetchPostsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
Typeahead.vue
<template>
<div>
<input
v-model="query"
#blur="reset"
type="text"
class="SearchInput"
:placeholder="placeholder">
<transition-group name="fade" tag="ul" class="Results">
<li v-for="item in items" :key="item.id">
<span #click="sendlink(item)">
<strong>{{ item.name }}</strong> - <small>{{ item.model }}</small><br>
<small>{{ item.description }}</small>
</span>
</li>
</transition-group>
<p v-show="isEmpty">Sorry, but we can't find any match for given term :( </p>
</div>
</template>
<script>
var axios = require("axios");
export default {
name: 'Typeahead',
props: {
modellink: {
type: Object,
required: false
},
source: {
type: [String, Array],
required: true
},
filterKey: {
type: String,
required: true
},
startAt: {
type: Number,
default: 3
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
items: [],
query: '',
taitem: ''
}
},
computed: {
lookup() {
if(this.query.length >= this.startAt) {
axios.get(this.source + '/' + this.query).then((res) => {
this.items = res.data;
return res.data;
});
}
},
isEmpty() {
if( typeof this.lookup === 'undefined' ) {
return false
} else {
return this.lookup.length < 1
}
}
},
methods: {
sendlink: function (taitem) {
this.$emit('selected-link', taitem);
},
reset() {
this.query = ''
}
}
}
</script>
In your PostList.vue, move the v-on:selected-link="onSelectedLink" from the div to typeahead like below. When emitting an event from child to parent, the listener on the parent needs to be on the child component tag for it to work.
<div #click="doit">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3"
v-on:selected-link="onSelectedLink">
</typeahead>