I want to have v-model and value set in one input. Here is my code:
<td>
<input v-model="invoice.items[n-1].net_price" type="number" step="0.01" min="1" form="invoice-form">
</td>
<td v-if="invoice.items[n-1].net_price && invoice.items[n-1].quantity">
<input :value="(invoice.items[n-1].net_price * invoice.items[n-1].quantity).toFixed(2)" type="text" form="invoice-form" readonly>
</td>
<td v-else>
<input type="text" readonly>
</td>
Problem is with 2nd input. Now it works like this:
You input value in net-price input
You input quantity
It calculates a value and set it in readonly input
Now I want to set v-model in this 2nd input, but when I try to do this I get an error.
Is there a way to set a model to this calculated value?
A more robust approach is to perhaps put your calculation logic in a method and use destructuring assignment to unpack the values off the individual items. In addition, the variables can also be assigned default values in the case that the unpacked values are undefined -- That way, you no longer need to worry about the computed values returning NaN since they will fallback to zeros when they are.
Try something like that:
new Vue({
data: () => ({
invoice: {
items: [
{
net_price: 30.05,
quantity: 4
},
{
net_price: 14.99,
quantity: 7
},
]
}
}),
methods: {
calculate({ net_price = 0, quantity = 0}) {
return (net_price * quantity).toFixed(2);
}
}
})
.$mount('#app');
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form id="invoice-form">
<table>
<thead>
<tr>
<th>Net Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) of invoice.items" :key="index">
<td>
<input v-model="item.net_price" type="number" step="0.01" min="1" form="invoice-form" />
</td>
<td>
<input v-model="item.quantity" type="number" min="0" form="invoice-form" />
</td>
<td>
<input :value="calculate(item)" form="invoice-form" readonly />
</td>
</tr>
</tbody>
</table>
</form>
</div>
Related
in my Vuejs3 project, there is a form in which the new row will be generated by pressing a button, so I put an array to handle new rows then I need to validate inputs, but after validation of the input of the array, the value didn't pass to model, but it works without the validation.. please help me to understand the mistake that I did.
The Form:
<table class="table table-stripped table-border">
<thead>
<tr>
<th>#</th>
<th style="width: 18%">
Medikament <span class="text-danger">*</span>
</th>
<th style="width: 9%">Milligram</th>
<th style="width: 9%">
Packung <span class="text-danger">*</span>
</th>
<th style="width: 7%">
Stück <span class="text-danger">*</span>
</th>
<th style="width: 19%">Apothekenname</th>
<th style="width: 24%">Adresse der Apotheke</th>
<th style="width: 14%">Postleitzahl</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in patientRequestForCreationDtos" :key="index">
<td>{{ index + 1 }}</td>
<td>
<input type="text" v-model="item.drugName" class="form-control" />
</td>
<td>
<input type="number" v-model="item.milligram" class="form-control" />
</td>
<td>
<input type="number" v-model="item.box" class="form-control" />
</td>
<td>
<input type="number" v-model="item.total" class="form-control" />
</td>
<td>
<input type="text" class="form-control" v-model="item.drugStoreName" :readonly="patientDetails.isElga == false" />
</td>
<td>
<input type="text" class="form-control" v-model="item.drugStoreAddress" :readonly="patientDetails.isElga == false" />
</td>
<td>
<input type="text" class="form-control" v-model="item.drugStorePostalCode" :readonly="patientDetails.isElga == false" />
</td>
<td>
<button type="button" #click="deleteRequestItemRow(index)" class="btn btn-sm btn-danger">
-
</button>
</td>
</tr>
</tbody>
</table>
The Array:
patientRequestForCreationDtos: [{
milligram: null,
box: null,
total: null,
drugStoreName: "",
drugStoreAddress: "",
drugStorePostalCode: "",
drugName: "",
}, ],
The validation function:
checkValidation() {
if (!this.patientRequestForCreationDtos.drugName) {
Swal.fire("drug name is required...");
return;
}
return true;
},
```js
---
it always says => drug name is required..
this.patientRequestForCreationDtos is array. maybe you can do this.
checkValidation(patientRequestForCreationDto) {
if (!patientRequestForCreationDto.drugName) {
Swal.fire("drug name is required...");
return;
}
return true;
},
if you'll have only one element in patientRequestForCreationDtos then you gotta choose first element in the array and then check its property
checkValidation() {
if (!this.patientRequestForCreationDtos[0].drugName) {
Swal.fire("drug name is required...");
return;
}
return true
},
also if your patientRequestForCreationDtos is always gonna be an array then you might find this helpful
I'm trying to calculate a value from 2 input boxes and then get the total of those input boxes. I'm then trying to get the all my amounts and total them and add them to the subtotal but the issue I'm having is that when I type in a number in the first box my output is NaN instead of 0 and I would like for it to show me a 0 instead.
Here is my code
<template>
<div class="content-header">
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-lg-12">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Unit</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr v-for="product in products">
<td>{{ product['name'] }}</td>
<td>
<input type="text" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
</td>
<td>
<input type="text" class="form-control" v-model="price[product['price']]" #change="calculateCost(product['name'])">
</td>
<td>
{{ cost[product['name']] }}
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>
Subtotal: {{ subTotal }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default{
props: [],
data(){
return {
products: [],
unit: {},
price: {},
cost: []
}
},
computed:{
subTotal(){
if(this.cost!== null)
{
if(Object.keys(this.cost).length !== 0){
return Object.keys(this.cost).reduce((carry, item) => {
carry+= Number(this.cost[item])
return carry;
}, Number(0))
}else{
return 0;
}
}
}
},
methods: {
getProducts(){
axios.get(`/api/product/all`).then(response => {
this.products = response.data.products;
});
},
calculateCost(item){
this.cost[item] = Number(this.unit[item]) * Number(this.price[item]);
},
},
mounted() {
this.getProducts();
}
}
</script>
Almost all type of inputs return a string. You can use
<input type="number" class="form-control" v-model="unit[product['unit']]" #change="calculateCost(product['name'])">
or
<input type="text" class="form-control" v-model.number="unit[product['unit']]" #change="calculateCost(product['name'])">
The problem is the v-model for unit and price are set to the different keys than the one given to calculateCost(), which causes the lookups to fail and results in NaN:
<input v-model="unit[product['unit']]" #change="calculateCost(product['name'])"> ❌
^^^^^^ ^^^^^^
<input v-model="price[product['price']]" #change="calculateCost(product['name'])"> ❌
^^^^^^^ ^^^^^^
<input v-model="unit[product['name']]" #change="calculateCost(product['name'])"> ✅
<input v-model="price[product['name']]" #change="calculateCost(product['name'])"> ✅
Setting the keys to product['name'] ensures the correct lookup for unit and price in calculateCost(). Since the user could enter invalid values (non-numeric strings) or omit a value, it's possible to get NaN, so it would be a good idea to add a NaN-check in calculateCost():
calculateCost(item) {
const unit = Number(this.unit[item]);
const price = Number(this.price[item]);
if (Number.isNaN(unit) || Number.isNaN(price)) return;
this.cost[item] = unit * price;
},
Also, you probably want the cost to react to user input, so switch from #change to #input:
<input v-model="unit[product['name']]" #input="calculateCost(product['name'])">
<input v-model="price[product['name']]" #input="calculateCost(product['name'])">
demo
I am learning Vue.
Now, I am trying to add data with the price and finally, it calculates total price:
Here is the HTML
<div id="app">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
Here is the Vue Instance:
new Vue({
el : '#app',
data : {
items: [
{ name: 'Rice', price : 12.60 },
{ name: 'Oil', price : 22.00 },
{ name: 'Mango', price : 32.50 },
{ name: 'Orange', price : 42.00 },
],
newItem : '',
newItemPrice : '',
},
computed: {
total() {
var total = 0;
this.items.forEach( item => {
total += parseFloat( item.price );
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem( index ) {
this.items.splice( index, 1 )
}
}
});
You can see it's by default showing item name and price. I want to add new item using the v-model called newItem But It's not adding the new item to the table
BUT
If I remove the Item Price column I mean this line:
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price">
</td>
then it's adding the new item perfectly :(
can you tell me what's wrong here?
See two issues with the fiddle:
There is no way to submit the form data
When pushing the price field was not added to the object
After fixing both of them it works well in this fiddle.
This happens because of browser implementation. As mentioned in W3C Specs:
When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.
But in case of multiple elements, the enter keypress does not trigger the form submit and thus you get this behaviour.
To resolve this you can simply use #keyup.enter.prevent="addItem" to listen to the enter keypress on each input and call the addItem() function like:
new Vue({
el: '#app',
data: {
items: [{name:"Rice",price:12.6},{name:"Oil",price:22},{name:"Mango",price:32.5},{name:"Orange",price:42}],
newItem: '',
newItemPrice: null,
},
computed: {
total() {
var total = 0;
this.items.forEach(item => {
total += parseFloat(item.price);
})
return total;
}
},
methods: {
addItem() {
this.items.push({
name: this.newItem,
price: 0
});
},
removeItem(index) {
this.items.splice(index, 1)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
<form #submit.prevent="addItem">
<table border="1" cellpadding="10" width="300">
<tr>
<td colspan="2"><strong>Add New Item</strong></td>
</tr>
<tr>
<td>
<input type="text" name="" v-model="newItem" placeholder="Item Name"
#keyup.enter.prevent="addItem">
</td>
<td>
<input type="number" name="" v-model="newItemPrice" placeholder="Item Price"
#keyup.enter.prevent="addItem">
</td>
</tr>
</table>
</form>
<br>
<table border="1" cellpadding="10" width="400">
<tr>
<th>Item Name</th>
<th>Item Price</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td>
<td><input type="number" name="" v-model="item.price"></td>
<td><button #click="removeItem(index)">X</button></td>
</tr>
<tr>
<td>Total</td>
<td><strong>{{ total }}</strong></td>
</tr>
</table>
</div>
You should put a new line in your form, my suggestion is to put just above the close form tag </form>:
<input type="submit" value="add">
Another fix to do is in your methods addItem()
addItem() {
this.items.push({
name: this.newItem,
price: this.newItemPrice
});
}
Where it is the number 0 you should provide the this.newItemPrice to it work properly.
The rows of a table are generated using a v-for loop over an array of objects in graphicState. I am trying to create a column of radio buttons. When a radio button is checked, this should set graphicState[index].selected to true.
This post is interesting, but how can I use it set graphicState[index].selected to true?
<form>
<div class="row">
<div class="col-md-12 " align="center">
<table class="table-striped" v-on:mouseleave="mouseleave()">
<thead>
<tr>
<th></th>
<th>Show</th>
<th>Select</th>
<th>Shape</th>
</tr>
</thead>
<tbody>
<tr v-for="(form, index) in graphicState" :key="index">
<td #click="removeDate(index)"><i class="far fa-trash-alt"></i></td>
<td>
<input type="checkbox" v-model="form.show">
</td>
<td>
<input type="radio" name="grp" id="grp" value="true" v-model="form.selected">
</td>
<td v-on:click="toggleShape(index)">
{{ form.shape }}
</td>
</tr>
</tbody>
</table>
<div v-for="(form, index) in graphicState " :key="index">
</div>
</div>
</div>
</form>
You can use #change event handler:
<input type="radio" name="grp" id="grp" value="true" v-model="form.selected" #change="onChange($event, index)">
then handle in method:
methods: {
onChange (event, index) {
this.graphicState[index].selected = event.target.value
this.graphicState = JSON.parse(JSON.stringify(this.graphicState))
}
}
The code you have already should set the graphicState[index].selected to true for the radio inputs, but the input values (and thus graphicState[index].selected through v-model) are never set to false, which is a problem if the user is allowed to change their mind to select a different input. This occurs because the radio input's change-event is only fired when the its checked property is set to a truthy value (upon selection).
One solution is to add a change-event handler that clears the .selected value for the non-selected inputs.
// template
<tr v-for="(form, index) in graphicState">
<input #change="onRadioChange(index)" ...>
</tr>
// script
onRadioChange(selectedIndex) {
this.graphicState
.filter((x,i) => i !== selectedIndex) // get non-selected inputs
.forEach(x => x.selected = false)
}
But there's still another problem if you're using HTMLFormElement's native submit. In the following template, when the v-model value is true, Vue sets the radio input's checked property to true, which tells the HTMLFormElement to use this particular input's value as the group value...
<input type="radio"
name="grp"
v-model="form.selected"
value="true">
All the radio inputs have the same value, so the receiver of the form data won't be able to tell which input is selected. To address this, assign a unique value to each input based on the iterator item. For example, you could use ID:
<input type="radio"
name="grp"
v-model="form.selected"
value="form.id">
new Vue({
el: '#app',
data() {
return {
graphicState: [
{id: 'A', selected: false, show: false},
{id: 'B', selected: false, show: false},
{id: 'C', selected: false, show: false},
]
}
},
methods: {
mouseleave() {},
removeDate() {},
toggleShape() {},
onRadioChange(selectedIndex) {
this.graphicState
.filter((x,i) => i !== selectedIndex)
.forEach(x => x.selected = false)
},
}
})
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<script src="https://unpkg.com/vue#2.6.8/dist/vue.min.js"></script>
<div id="app">
<form method="post" action="//httpbin.org/post">
<div class="row">
<div class="col-md-12" align="center">
<table class="table-striped" v-on:mouseleave="mouseleave()">
<thead>
<tr>
<th></th>
<th>Show</th>
<th>Select</th>
<th>Shape</th>
</tr>
</thead>
<tbody>
<tr v-for="(form, index) in graphicState" :key="form.id">
<td #click="removeDate(index)"><i class="far fa-trash-alt"></i></td>
<td>
<input type="checkbox" v-model="form.show">
</td>
<td>
<input type="radio" name="grp" :value="form.id" v-model="form.selected" #change="onRadioChange(index)">
</td>
<td v-on:click="toggleShape(index)">
{{ form.shape }}
</td>
</tr>
</tbody>
</table>
<pre>{{graphicState}}</pre>
</div>
</div>
<button>Submit</button>
</form>
</div>
I'm using laravel and vuejs.
One of the things I want to be able to do, is to allow the user to create muliple address when they click the button.
Here the sample code:
<table>
<tr>
<th>POSTAL CODE</th>
<td><input type="text" name="postal_code[]" v-model="" /></td>
</tr>
<tr>
<th>City</th>
<td>
<select name="city[]" v-model="">
<option>ABC</option>
</select>
</td>
</tr>
<tr>
<th>District</th>
<td><input type="text" name="District[]" v-model="" /></td>
</tr>
<tr>
<th>Street</th>
<td><input type="text" name="Street[]" v-model="" /></td>
</tr>
<tr>
<th>Name/th>
<td>
last_name<input type="text" name="last_name[]" v-model="" />
first_name<input type="text" name="first_name[]" v-model="" />
</td>
</tr>
</table>
<button v-on:click="add-form">ADD</button>
EDIT Sorry because my question was not clear enough.
I want to duplicate the whole table, not only one input, and everytime user click the button, i want to show one more table (the old table with old input has not be changed)
I found the solution.
Thank you so much!
All you need to do is to create an empty array of addresses and then push the input into this array on button click. Here's an example:
Jsfiddle: https://jsfiddle.net/zyh4jvuf/
<div id="app">
<input type="text" v-model="addressInput">
<button v-on:click="addAddress">add</button>
<!-- Displaying addresses -->
<div v-for="address in addresses">
{{address}}
</div>
</div>
new Vue({
el: '#app',
data () {
return {
addressInput: '',
addresses: []
}
},
methods: {
addAddress () {
this.addresses.push(this.addressInput)
// clear the input
this.addressInput = ''
}
}
})
Do you want to "duplicate" the whole table or just a single input? I guess the second want. In that case, i would rather push the info to an array like this:
<template>
<div class="formApp">
<input type="text" name="steet" v-model="street">
<button #click="addStreet">add</button>
<div v-for="str in streetArray">
<div #click="removeStreet($event)">{{ str }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'formApp',
data() {
return {
streetArray: [],
street: '',
}
},
methods: {
addStreet() {
this.streetArray.push(this.street);
this.street = '';
},
removeStreet(event) {
var index = this.streetArray.indexOf(event.target.innerText);
index > -1 ? this.streetArray.splice(index, 1):'';
}
}
}
</script>
The addStreet will add a new street everytime you click the button and then it will clear the input. Then, if the user wants to delete the street, it will have to click over the street name. That way of deleting things is not user friendly at all so you could add a icon to each street name to declare that possibility.
Also, it was just an hint for you because there can be errors while writing streets names and the user needs a way to delete them. Having a bunch of inputs for wrong streets or not being able to delete the ones you added would be a total chaos. Anyways, there you go.
You can try the following code
new Vue({
el: '#example-3',
data:{
title:"Lists",
item:{},
items:[]
},
methods: {
addform: function () {
this.items.push(this.item);
this.item={};
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="example-3">
<table>
<tr>
<th>POSTAL CODE</th>
<td><input type="text" name="postal_code" v-model="item.postal_code" /></td>
</tr>
<tr>
<th>City</th>
<td>
<select name="city" v-model="item.city">
<option>ABC</option>
</select>
</td>
</tr>
<tr>
<th>District</th>
<td><input type="text" name="District" v-model="item.District" /></td>
</tr>
<tr>
<th>Street</th>
<td><input type="text" name="Street" v-model="item.Street" /></td>
</tr>
<tr>
<th>Name</th>
<td>
last_name<input type="text" name="last_name" v-model="item.last_name" /><br/>
first_name<input type="text" name="first_name" v-model="item.first_name" />
</td>
</tr>
</table>
<button v-on:click="addform">ADD</button>
<br/>
<h2>{{title}}</h2>
<table border="1" v-if="items.length>0">
<tr v-for="(value,index) in items">
<td>{{value.postal_code}}</td>
<td>{{value.city}}</td>
<td>{{value.District}}</td>
<td>{{value.last_name}}</td>
<td>{{value.first_name}}</td>
</tr>
</table>
</div>