Auto fill input based on select dropdown in Vue JS - javascript

I'm looking solution for how to auto fill input in vue js. I have a form which include of input type text, select dropdown, quantity, etc. I want when the select dropdown is selected, then the quantity of vCPU, vRAM, and Storage Capacity will be automatically filled with the value according to the selected Server Flavor.
I tried to choose Flavor Server with Flavor 1 options, vCPU should immediately be filled with a value 4, vRAM should be filled with a value 2, and storage capacity should be filled with a value 10. But the quantity does not appear.
But in the price estimation the numbers are correct, namely vCPU (4), vRAM (2), Storage Capacity (10)
I'm confused, to put the if conditional in the <base-quantity> at the #updateQuantity custom event or in v-if attribute. Is there anyone here that can help me solve this problem?
The full source code is in this codesandbox => https://codesandbox.io/s/suspicious-almeida-rjyy9
Lite.vue
<template>
<div class="container">
<h2 class="font-size-26 txt-secondary">Deka Flexi</h2>
<div class="row">
<div class="col-12">
<form #submit.prevent="submitFormLite">
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Commitment Type</label>
<select name="commitment" id="" class="select-custom" v-model="selectedCommitTypeLite">
<option v-for="ctl in commitTypeLite" :key="ctl.name" :value="ctl.value">{{ ctl.name }}</option>
</select>
</div>
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Server Name</label>
<input type="text" name="name" class="custom-input" v-model="serverNameLite" />
</div>
<div class="form-group form-group--border">
<label class="font-size-22 txt-secondary txt-semibold">Server Flavor</label>
<select name="storage-type" id="" class="select-custom" v-model="selectedServerFlavorLite">
<option v-for="sfl in serverFlavorLite" :key="sfl.name" :value="sfl.value">{{ sfl.name }}</option>
</select>
</div>
<h6 class="font-size-22 txt-secondary txt-semibold">
Components
</h6>
<div class="row form-group--border">
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">vCPU (GHz)</div>
<base-quantity #updateQuantity="updateCpuLite"></base-quantity>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">vRAM (GB)</div>
<base-quantity #updateQuantity="updateRamLite"></base-quantity>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">Storage Type</label>
<select name="storage-type" id="" class="select-custom" v-model="selectedStorageTypeLite">
<option v-for="stl in storageTypeLite" :key="stl.name" :value="stl.value">{{ stl.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">Storage Capacity (GB)</div>
<base-quantity #updateQuantity="updateCapacityLite" v-model="updateCapacityLite"></base-quantity>
</div>
</div>
</div>
<div class="row pt-4">
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">Public IP</label>
<select name="public-ip" id="" class="select-custom" v-model="selectedPublicIpLite">
<option v-for="pil in publicIpLite" :key="pil.name" :value="pil.value">{{ pil.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<label class="font-size-18 txt-secondary">OS Type</label>
<select name="os-lite" id="" class="select-custom" v-model="selectedOsLite">
<option v-for="ol in osLite" :key="ol.name" :value="ol.value">{{ ol.name }}</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="form-group text-center">
<div class="font-size-18 txt-secondary">Quantity</div>
<base-quantity #updateQuantity="updateQuantityLite"></base-quantity>
</div>
</div>
</div>
<div class="form-group mt-4 text-center">
<button class="button button__add" #click="addToCart">Submit</button>
</div>
</form>
</div>
</div>
</div>
</template>
<script>
import BaseQuantity from "../base/BaseQuantity.vue";
export default {
components: {
BaseQuantity,
},
data() {
return {
serverNameLite: '',
storageTypeLite: [
{
name: "Storage Type 1",
value: 100
},
{
name: "Storage Type 2",
value: 120
}
],
publicIpLite: [
{
name: "Yes",
value: 120
},
{
name: "No",
value: 20
}
],
osLite: [
{
name: "OS 1",
value: 80
},
{
name: "OS 2",
value: 100
},
{
name: "OS 3",
value: 120
}
],
serverFlavorLite: [
{
name: "Flavor 1",
value: "flavor-1"
},
{
name: "Flavor 2",
value: "flavor-2"
},
{
name: "Flavor 3",
value: "flavor-3"
}
],
commitTypeLite: [
{
name: "Commitment Type 1",
value: 80
},
{
name: "Commitment Type 2",
value: 100
},
{
name: "Commitment Type 3",
value: 120
}
],
selectedStorageTypeLite: "",
selectedPublicIpLite: "",
selectedOsLite: "",
selectedCommitTypeLite: "",
selectedServerFlavorLite:""
};
},
watch: {
serverNameLite: function() {
this.$store.commit('setServerNameLite', this.serverNameLite);
},
selectedStorageTypeLite: function() {
let storageTypeLite = this.storageTypeLite.find((storageTypeLite) => storageTypeLite.value == this.selectedStorageTypeLite);
this.$store.commit('setStorageTypeLite', storageTypeLite);
},
selectedPublicIpLite: function() {
let publicIpLite = this.publicIpLite.find((publicIpLite) => publicIpLite.value == this.selectedPublicIpLite);
this.$store.commit('setPublicIpLite', publicIpLite);
console.log(publicIpLite);
},
selectedOsLite: function() {
let osLite = this.osLite.find((osLite) => osLite.value == this.selectedOsLite);
this.$store.commit('setOsLite', osLite);
},
selectedCommitTypeLite: function() {
let commitTypeLite = this.commitTypeLite.find((commitTypeLite) => commitTypeLite.value == this.selectedCommitTypeLite);
this.$store.commit('setCommitTypeLite', commitTypeLite);
},
selectedServerFlavorLite: function() {
let serverFlavorLite = this.serverFlavorLite.find((serverFlavorLite) => serverFlavorLite.value == this.selectedServerFlavorLite);
this.$store.commit('setServerFlavorLite', serverFlavorLite);
if(this.selectedServerFlavorLite == "flavor-1"){
this.updateCpuLite(4);
this.updateRamLite(2);
this.updateCapacityLite(10);
}
},
},
methods: {
async addToCart() {
let isLiteEmpty = await this.$store.dispatch('isLiteEmpty');
if(!isLiteEmpty) {
this.$store.commit('calculateLiteCost');
this.$store.commit('setLite', this.$store.getters.getLiteState);
this.$store.commit('calculatePrice');
}
},
updateCpuLite(val) {
this.$store.commit('setCpuLite', {qty: val, value: 100});
console.log(val);
},
updateRamLite(val) {
this.$store.commit('setRamLite', {qty: val, value: 100});
console.log(val);
},
updateCapacityLite(val) {
this.$store.commit('setCapacityLite', {qty: val, value: 100});
console.log(val);
},
updateQuantityLite(val) {
this.$store.commit('setQuantityLite', {qty: val, value: 100});
console.log(val);
},
},
};
</script>
selectedServerFlavorLite: function() {
let serverFlavorLite = this.serverFlavorLite.find((serverFlavorLite) => serverFlavorLite.value == this.selectedServerFlavorLite);
this.$store.commit('setServerFlavorLite', serverFlavorLite);
if(this.selectedServerFlavorLite == "flavor-1"){
this.updateCpuLite(4);
this.updateRamLite(2);
this.updateCapacityLite(10);
}
},
BaseQuantity.vue
<template>
<div class="quantity" :class="disabled ? 'quantity__untoggle' : 'quantity__toggle'">
<button type="button" #click="decrement" class="btn quantity__decrement" :disabled="disabled">-</button>
<input type="text" class="quantity__value" :value="quantity" :disabled="disabled" readonly>
<button type="button" #click="increment" class="btn quantity__increment" :disabled="disabled">+</button>
</div>
</template>
<script>
export default {
props : ['disabled'],
data(){
return{
quantity: null
}
},
watch:{
quantity :function(val){
this.$emit('updateQuantity',val);
}
},
methods :{
increment () {
this.quantity++
},
decrement () {
if(this.quantity === 0) {
alert('Negative quantity not allowed')
} else {
this.quantity--
}
}
}
}
</script>

There are multiple ways, but it's all based on how your data is stored and connected through components.
Let's start from BaseQuantity.vue:
data() {
return {
quantity: 0 // you have a local saved state of the quantity
}
},
methods: {
increment() {
this.quantity++ // when user hits something, you increment the LOCAL state
},
}
watch: {
quantity: function(val) { // when the LOCAL value updates
this.$emit('updateQuantity', val); // you emit event it has been updated
}
}
Basically each of your Base Quantity components defines its state (starting from 0), and then tracks its own actions that update that state.
You use those components like
<base-quantity #updateQuantity="updateServer"
And the method calls Vuex to store the new value (gotten from the component, equals to it's internal state):
updateServer(val) {
this.$store.commit('setServer', {qty: val, value: 100});
}
Your first issue is that each of those Base Quantity components define their own initial state, which is internal and separate. Currently, there's no real way to tell any of those "your value is X", it's quite the opposite - they tell the parent "I've updated my value".
In order to do so, you must somehow set the initial value. The very basic approach would be to pass the initial value to the component:
props : ['disabled', 'initialValue'],
data(){
return {
quantity: this.initialValue
}
},
Problem two now is that you don't only need an initial value, you need to set the value from outside, whenever the user selects a dropdown option. But you would also like to keep track of manual updates on the component. So you need a two-directional binding of the values (set and update). That's where the v-model comes in handy. Here's a good article explaining how it worked before, and how it works now: https://v3-migration.vuejs.org/breaking-changes/v-model.html#overview Basically you'll use it like that:
<base-quantity v-model="serverQuantity" />
<!-- would be shorthand for: -->
<base-quantity
:modelValue="serverQuantity"
#update:modelValue="serverQuantity= $event"
/>
You don't store the data in your Calculator component - you store it in Vuex. Now this is the part where you have a lot of solutions and you need to be careful how you design your data flow. I would go with the simplest one:
Use store getters to instruct Base Quantity what its value is: <base-quantity :value="$store.getters.serverQuantity" />. This is a reactive property that would be updated when the store updates its value for server quantity. If you don't have getters you could use the state instead, but that's not suggested.
Remove local variable for quantity, and just use the passed property: <input :value="value" />
Upon update (clicking on a button), emit an event with the new value without updating it locally: increment() { this.$emit('updateQuantity', this.value + 1)
In your handler, commit the update as of now
In order to handle dropdown selection, if you use the approach described above, you just need to wait for the user input (dropdown selection) and populate the store with all the needed fields. Since they are passed to each component, the value would be automatically populated:
watch: {
selectedPackage: function() {
let pack = this.storagePackage.find((pack) => pack.value == this.selectedPackage);
this.$store.commit('setPackage', pack);
// here you should somehow map the pack to the values you'd like to populate
// let's say package "One" (value: 30) means you'd have 8 vRAM, then:
this.updateRam(package.ram);
// this would call this.$store.commit('setRam', { qty: 8, value: 100 });
this.updateCapacity(100);
this.updateCpu(5);
// all the other fields you'd like to update, with the specific values this PACK has (I haven't seen any map for plan -> values)
},
}
p.s. I'm not entirely sure why you store qty: val, value: 100 everywhere, but probably you have some reason about it.
With this approach you'll have a single source of truth about the data. And separate components that just say "I want this value (whatever it is) to be either incremented or decremented". So the component is completely isolated of any knowing of the real business logic of modification and storage, as well as the property name.
The parent is the one that handles data both ways - first it sends it to each component, and second - it handles the user actions, which are then commited to a Vuex store (single point of truth).

You can set values using the v-model but this is a different scenario where the values are based on the selected events. So I reached this solution where you can create a function that sets the values in custom mode
Try using this:
setValues: function () {
this.specifications === this.specifications[1]
? (this.os = this.os[0].name)
: (this.os = '');
You can pass in other Databases, StorageType, Firewall values as well.

Related

vuejs remove key and value from URL based on conditions

I am making a simple filter and In this filter I am pushing query parameters to URL and appending them. If user choose a particular option, I want to modify the query string and I need the help here.
My URL is like this : http://app.test/home?preference=onsite&place=Aus&place=Mus
and if user choose wfh instead of onsite for preference I want to remove place from URL and want this URL: http://app.test/home?preference=wfh
my code:
<template>
<div>
<div class="mt-2">
</div>
<div class="mt-2" v-if=" this.$route.query.preference == 'onsite'">
<label>Available for work</label>
<div class="form-control" >
<input type="text">
<div class="options" style="max-height:140px;overflow:scroll">
<ul class="" style="max-height:40px;">
<li v-for="place in places" :key="place.id">
<input type="checkbox" v-model="filter.place"
:value="place.name">{{ place.name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
places: [
{
id: 1,
name: "Aus",
},
{
id: 2,
name: "Mus",
},
],
filter: {
preference: this.$route.query.preference,
place:[],
},
};
},
watch: {
filter: {
handler() {
const query = this.filter
this.$router.push({
query: query,
});
},
deep: true,
},
},
};
</script>
I wrote below code to check if preference is wfh and update URL but this is not working
if (this.$route.query.preference == 'wfh') {
this.$route.query.place='';
this.$router.replace({ query: query });
}
You can extract the rest of the queries and replace with them, it same like delete place query.
if (this.$route.query.preference == 'wfh') {
const { place, ...query} = this.$route.query;
this.$router.replace({ query: query });
}

How to define a starting point in a v-for loop in VueJS

I have a group of fields that are generated in a loop an indefinite number of times. Basically 2 input fields, a label, and a delete button. Then another button that says Add More.
Every time they hit that, one more set of the above-mentioned appears. They can do this as many times as they want. It looks like this. (I'm using quasar, hence why the q-"" elements)
// Click more button
<q-btn #click="onAddBarcodes" icon="add" no-caps color="primary">Add More</q-btn>
// Form
<div v-for="(barcode, index) in barcodes" :key="index">
<div class="row q-pr-lg items-center">
<div class="col-3">
<label class="text-weight-medium">Starting Roll #:</label>
<q-input v-model="barcode.start" :id="barcode.id"></q-input>
</div>
<div class="col-3">
<label class="text-weight-medium">Ending Roll #:</label>
<q-input v-model="barcode.end" :id="barcode.id"></q-input>
</div>
<div class="col-5">
<label class="text-weight-medium">Agency Name:</label>
<div v-if="barcode.agencyName">
{{ barcode.agencyName }}
</div>
</div>
<div class="col-1">
<q-btn #click="onDeleteBarcodes(barcode.id)" icon="close"/>
</div>
</div>
</div>
export default {
data() {
return {
barcodes: []
}
},
methods: {
onAddBarcodes() {
const newBarcode = {
id: Math.random() * Math.random() * 1000,
start: "",
end: "",
agencyName: ""
};
this.barcodes.push(newBarcode);
},
onDeleteBarcodes(id) {
this.barcodes = this.barcodes.filter(barcode => barcode.id !== id);
},
}
}
Now this works at its most basic form. What I want to do is to load the component with a set number of iterations already started. For example, 5 of them.
The Add More button will add the 6th on forward if they need more. The delete button should delete down to zero if they wanted to (not that they would) but it should definitely delete down to 1 at least.
How can I start the v-for at a specific number of iteration?
You can add 5 items to barcodes on created() vue instance like:
created() {
[...Array(5).keys()].forEach(() => this.onAddBarcodes())
}
You can add mounted hook and initialize collection with 5 elements here
export default {
data() {
return {
barcodes: []
}
},
mounted() {
this.barcodes = Array(5).fill(0).map(pr => ({
id: Math.random() * Math.random() * 1000,
start: "",
end: "",
agencyName: ""
}))
},
methods: {
onAddBarcodes() {
const newBarcode = {
id: Math.random() * Math.random() * 1000,
start: "",
end: "",
agencyName: ""
};
this.barcodes.push(newBarcode);
},
onDeleteBarcodes(id) {
this.barcodes = this.barcodes.filter(barcode => barcode.id !== id);
},
}
}
You can call the onAddBarcodes n times in beforeMount:
beforeMount(){
const n = 5; // Or any number
Array.from(new Array(n)).forEach(el => this.onAddBarcodes());
}

How to use v-for with vuex to render forms if I need v-bind?

I have the following UI:
I will try to explain what is going on here. I have "Add message" button. When I click on the button I have new form with message: title, body, image, language (just multiple select via this plugin). I have clicked twice - I have 2 messages. Simple.
I don't use vue router. Implementation of my routing is with the help of backend. It means that for each route I have new vuex state.
I'm going to keep my messages in vuex, but it's impossible to use v-model for this case.
So, I will show my code.
store:
export const store = new Vuex.Store({
state: {
messages: [],
// more props are here ...
},
mutations: {
setMessages(state, messages) {
state.messages = messages;
},
// more setters are here
},
getters: {
getMessages: state => {
return state.messages;
},
// more getters are here
},
actions: {
updateMessagesAction: function({commit}, value) {
console.log(value)
},
}
});
Messages component:
<template>
<div>
<button class="btn btn-outline-info" #click.prevent="createNewMessage">
<i class="fa fa-language"/> Add message
</button>
<div>
// now it works with local state, but I need to work with vuex
<div v-for="(message, index) in messages">
<button class="btn btm-sm btn-danger" #click="deleteMessage(index, message)"><i class="fa fa-remove"/>
</button>
<b-collapse collapsed :id="`collapse-${index}`">
<form>
<div class="form-group">
<label class="typo__label">Languages</label>
<multiselect
v-model="message.languages"
:options="getLanguagesOptions"
:multiple="true"
:close-on-select="true"
:clear-on-select="false"
:preserve-search="true"
placeholder="Languages"
label="name"
track-by="id"
>
</multiselect>
</div>
<div class="form-group">
<label for="title" class="typo__label">Title</label>
<input type="text" id="title" class="form-control" autocomplete="off" ??? how to bind it to vuex ???? I dont understand :(((>
</div>
<div class="form-group">
<label for="text" class="typo__label">Body</label>
<textarea class="form-control" id="text" ??? how to bind it to vuex ???? I dont understand :(((/>
</div>
<div class="form-group">
<div id="upload-image">
<div v-if="!message.imageSrc">
<h2>Image</h2>
<input type="file" ref="file" #change="onFileChange($event, message)">
</div>
<div v-else>
<img :src="message.imageSrc"/>
<button #click.prevent="removeImage($event, message)">Remove</button>
</div>
</div>
</div>
<hr class="mb-2">
</form>
</b-collapse>
</div>
</div>
</div>
</template>
<script>
// imports
export default {
async created() {
// set languages from servert to vuex
let res = (await axios.post(this.urlForGettingLanguagesFromServerProp)).data;
this.$store.commit('setLanguagesOptions', res);
},
name: "MessagesComponent",
props: {
urlForGettingLanguagesFromServerProp: String,
uploadImageUrl: String,
deleteImageUrl: String,
selectedLanguagesIdsProp: {
type: Array,
default: () => []
},
},
methods: {
...mapMutations(['setLanguagesSelected']),
...mapActions(['updateMessagesAction']),
createNewMessage: function () {
let message = {
languages: [],
languagesIds: [],
title: "",
text: "",
imageSrc: "",
imageDbId: 0
};
this.messages.push(message);
},
deleteMessage: function (index, message) {
this.removeImage("", message);
this.messages.splice(index, 1);
},
onFileChange: async function (e, message) {
// this method add send image on server and save to state db image id ant path
},
removeImage: function (event, message) {
// remove image from server
}
},
computed: {
...mapGetters(['getLanguagesOptions', 'getMessages'])
},
watch: {
messages: {
deep: true,
immediate: true,
handler(val, oldVal) {
let message = JSON.parse(JSON.stringify(val));
this.$store.commit("setMessages", message);
}
}
},
data() {
return {
messages: [],
}
}
}
</script>
I call this component in a parent component. In the parent component I initiate vuex during update operation.
As you can see this component works with local state and synchronize local state with vuex. It's ok for Create operation. I can send messages to vuex, then I can take it in the parent component with other information and send it on the server. But what to do with Update? I have data from the server in the parent component but local state, of course, is empty for the Messages component. How to bind all messages to vuex and have ability to change each message separately? I mean, for example, to change title of the first message and to have it in vuex immediately?
Please, help me improve this component.

How to track changed Valued from array of objects in vuejs

I have an addRows array which contains a group of objects.
These objects are added dynamically when using click + button.
Then they are pushed to the addRows array; after that, the user fills these objects.
Every object has many input values such as price and quantity.
I need to change the price when the quantity changes, but the problem is if the value was added before, the returned changed item is empty.
This happens because the new value was added before in the old values array.
As shown in the image third value same first so the array returned empty value of third item
I tried foreach and for also map but I'm facing the same problem.
computed: {
originalDistValue(a , b) {
return this.addRows.map(i => {
return i.dist
})
}
}, // close computed
watch: {
originalDistValue(a , b) {
let newVal = a.filter( obj => {
return b.indexOf(obj) == -1;
})
let element = this.addRows.filter( i => {
return i.dist == newVal;
})
deep: true
console.log(newVal)
for(let i in element) {
element[i].cit = 1;
}
}
}
At first create a child component for each row
Pass each row to component
Handle the single object inside the component instead of handling an array of objects in parent
See this example:
Vue.component('child', {
props: {
value: { type: Object, default: () => ({}) }
},
data: function() {
return {
selfValue: null
}
},
watch: {
value: {
handler(value) {
this.selfValue = value
},
immediate: true,
deep: true
},
selfVaue: {
handler(value) {
// you can also validate value then emit it
this.$emit('input', value)
},
deep: true
},
'selfValue.quantity'() {
this.selfValue.price = 0
}
},
template: `
<div class="d-flex justify-content-between">
<label>
Title:
<input type="text" v-model="selfValue.title" class="form-control" />
</label>
<label>
Quantity:
<select v-model="selfValue.quantity" class="form-control">
<option value="A">A</option>
<option value="B">B</option>
</select>
</label>
<label>
Price:
<input type="tel" v-model="selfValue.price" class="form-control" />
</label>
</div>
`
})
new Vue({
el: '#app',
data: {
rows: []
},
methods: {
add() {
this.rows.push({ title: '', quantity: '', price: 0 })
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div id="app" class="container">
<h2 class="mb-3">Rows:</h2>
<div v-for="row,i in rows">
<child v-model="row"></child>
<hr class="mt-1 mb-1" />
</div>
<button #click="add" class="mb-3">
ADD
</button>
<pre>{{ rows }}</pre>
</div>

Updating an input's bound value when edited

I'm working on a basic to-do application. Each to-do/task item gets listed as an input item in a Vue <list-item> component, and the <list-item>s are displayed with a v-for pointing to a tasks array.
I'm trying to allow the user to edit each task input, and upon changing the value, have this update the array item (rather than just the input itself). My #change event on the input is firing, but I'm at a loss as to what to do after this point.
https://jsfiddle.net/xbxm7hph/
HTML:
<div class="app">
<div class="add-control-area columns is-mobile is-multiline">
<responsive-container>
<div class="field is-grouped">
<div class="control is-expanded">
<input class="input add-control-text" type="text" placeholder="New Task" v-model="newTask" v-on:keyup.enter="addTask">
</div>
<div class="control">
<a class="button is-white add-control-button" #click="addTask" :disabled="!isThereText">Add Task</a>
</div>
</div>
</responsive-container>
<responsive-container>
<list-item v-for="task, index in tasks" :item="task" :index="index" #task-completed="completeTask(index)" #task-deleted="deleteTask(index)" ></list-item>
</responsive-container>
</div>
</div>
JS:
Vue.component('list-item', {
props: ['item', 'index'],
template: `<div class="task-wrapper">
<input class="task" :value="item" #change="updateTask()">
<div class="task-control delete-task" #click="deleteTask()"></div>
<div class="task-control complete-task" #click="completeTask()"></div>
</div>
`,
methods: {
completeTask: function() {
this.$emit('task-completed', this.index);
},
deleteTask: function() {
this.$emit('task-deleted', this.index);
},
updateTask: function() {
console.log('changed');
}
}
});
Vue.component('responsive-container', {
template: `
<div class="column is-4-desktop is-offset-4-desktop is-10-tablet is-offset-1-tablet is-10-mobile is-offset-1-mobile">
<div class="columns is-mobile">
<div class="column is-12">
<slot></slot>
</div>
</div>
</div>
`
});
var app = new Vue({
el: '.app',
data: {
tasks: [],
completedTasks: [],
newTask: ''
},
methods: {
addTask: function() {
if(this.isThereText) {
this.tasks.push(this.newTask);
this.newTask = '';
this.updateStorage();
}
},
completeTask: function(index) {
this.completedTasks.push(this.tasks[index]);
this.tasks.splice(index, 1);
this.updateStorage();
},
deleteTask: function(index) {
this.tasks.splice(index, 1);
this.updateStorage();
},
updateStorage: function() {
localStorage.setItem("tasks", JSON.stringify(this.tasks));
}
},
computed: {
isThereText: function() {
return this.newTask.trim().length;
}
},
// If there's already tasks stored in localStorage,
// populate the tasks array
mounted: function() {
if (localStorage.getItem("tasks")) {
this.tasks = JSON.parse(localStorage.getItem("tasks"));
}
}
});
Use a v-model directive on your <list-item> component, instead of passing in an item property. You will also need to pass in a reference from the array (tasks[index]), because task in this scope is a copy that is not bound to the element of the array:
<list-item v-for="task, index in tasks" v-model="tasks[index]"></list-item>
In your component definition for the list item, you'll need to now take in a value prop (this is what gets passed when using v-model) and set a data property item to that value. Then, emit an input event on the change to pass the item value (this is what the component is listening for when using v-model):
Vue.component('list-item', {
props: ['value'],
template: `<div class="task-wrapper">
<input class="task" v-model="item" #change="updateTask"></div>
</div>
`,
data() {
return {
item: this.value,
}
},
methods: {
updateTask: function() {
this.$emit('input', this.item);
}
}
});
Here's a fiddle with those changes.
As Bert Evans mentioned, even though this works, Vue requires that components using the v-for directive also use a key attribute (you will get a warning from Vue otherwise):
<list-item
v-for="task, index in tasks"
:key="index"
v-model="tasks[index]"
></list-item>
Also, realize that the index variable in a v-for scope can change, meaning that the item at index 1 might change to index 4 and this can pose some problems as the application gets more complex. A better way would be to store items as an object with an id property. This way you can have an immutable id associated with the item.
You can pass the index and new value to your change event handler:
<input class="task" :value="item" #change="updateTask(index, $event)">
Then access them accordingly:
updateTask: function(index, event) {
console.log(index);
console.log(event.target.value);
}

Categories