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>
Advertisement
Answer
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
- Use store getters to instruct Base Quantity what its value is:
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).