How to calculate the total in vue component ? Vue.JS 2 - vue.js

My vue component, you can see below :
<template>
<div>
<div class="panel-group" v-for="item in list">
...
{{ total = 0 }}
<tr v-for="product in item.products">
...
<td>
<b>Price</b><br>
<span>{{ product.quantity * product.price }}</span>
</td>
</tr>
{{ total += (product.quantity * product.price) }}
<tr>
<td colspan="3" class="text-right">
<b>Total: {{ total }} </b>
</td>
</tr>
</div>
</div>
</template>
<script>
export default {
...
computed: {
list: function() {
return this.$store.state.transaction.list
},
...
}
}
</script>
I try like above code
But, seems it still wrong
How can I solve it correctly?
I'm still newbie in vue.js 2

Since, TypeError: this.$store.state.transaction.list.reduce is not a function is an error marked in Frank's answer I presume this.$store.state.transaction.list is not an Array but an object as v-for iterates through both.
total: function() {
var list = this.$store.state.transaction.list
var sum = 0
for(var listProps in list) {
list[listProps].products.forEach(function (product) {
sum += product.pivot.quantity * product.pivot.price
})
}
return sum;
}

Use another computed property
<script>
export default {
...
computed: {
list: function() {
return this.$store.state.transaction.list
},
total: function() {
return this.$store.state.transaction.list.reduce(function(sum, item) {
sum += item.products.reduce(function(tmp, product) { tmp += product.quantity * product.price; return tmp; }, 0);
return sum;
}, 0);
}
...
}
}
</script>
Use a nested Array.reduce to get the total of your structure where the list has many items and an item has many products

Related

Unable to register child component in vue.js

Getting following error.
Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I am not sure what is wrong with the code. I have followed this link https://michaelnthiessen.com/solve-unknown-custom-element-vue/
I have used Local registration for child component. ( RobotBuilder.vue)
<template>
<div class="content">
<button class="add-to-cart" #click="addToCart()">Add to Cart</button>
<div class="top-row">
<PartSelector />
</div>
<div class="middle-row">
<PartSelector />
<PartSelector />
<PartSelector />
</div>
<div class="bottom-row">
<PartSelector />
</div>
<div>
<table>
<thead>
<tr>
<th>Robot</th>
<th class="cost">Cost</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="(robot,index) in cart" :key="index">
<td>{{robot.head.title}}</td>
<td>{{robot.cost}}</td>
<td>
<button #click="removeItem([index])">X</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import availableParts from '../data/parts';
import { PartSelector } from './PartSelector.vue';
export default {
name: 'RobotBuilder',
components: { PartSelector },
data() {
return {
availableParts,
cart: [],
selectedRobot: {
head: {},
leftArm: {},
rightArm: {},
torso: {},
base: {},
},
};
},
computed: {},
methods: {
addToCart() {
const robot = this.selectedRobot;
const cost = robot.head.cost
+ robot.leftArm.cost
+ robot.torso.cost
+ robot.rightArm.cost
+ robot.base.cost;
this.cart.push({ ...robot, cost });
},
removeItem(index) {
this.cart.splice(index, 1);
},
},
};
</script>
<style scoped>
</style>
PartSelector.vue
<template>
<div class="part">
<img :src="selectedPart.src" title="arm"/>
<button #click="selectPreviousPart()" class="prev-selector"></button>
<button #click="selectNextPart()" class="next-selector"></button>
<span class="sale" v-show="selectedPart.onSale">Sale!</span>
</div>
</template>
<script>
import availableParts from '../data/parts';
const parts = availableParts.heads;
function getPreviousValidIndex(index, length) {
const deprecatedIndex = index - 1;
return deprecatedIndex < 0 ? length - 1 : deprecatedIndex;
}
function getNextValidIndex(index, length) {
const incrementedIndex = index + 1;
return incrementedIndex > length - 1 ? 0 : incrementedIndex;
}
export default {
name: 'PartSelector',
data() {
return { selectedPartIndex: 0 };
},
computed: {
selectedPart() {
return parts[this.selectedPartIndex];
},
},
methods: {
selectNextPart() {
this.selectedPartIndex = getNextValidIndex(
this.selectedPartIndex,
parts.length,
);
},
selectPreviousPart() {
this.selectedPartIndex = getPreviousValidIndex(
this.selectedPartIndex,
parts.length,
);
},
},
};
</script>
You are exporting as default but importing as named import.
In Robot builder, import like this :
import PartSelector from './PartSelector.vue';

Dynamic v-model problem on nuxt application

I am trying to create a dynamic v-model input. All seems well except for the following.
The on click event that triggers the checkAnswers method only works if you click out of the input then click back into the input and then press the button again. It should trigger when the button is pressed the first time.
Does anyone have any ideas? Thanks in advance.
<template>
<div class="addition container">
<article class="tile is-child box">
<div class="questions">
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n] }} + {{ randomNumberB[n] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n]">
<p>{{ outcome[n] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answer</button>
</div>
</article>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
randomNumberB: [] = Array.from({length: 40}, () => Math.floor(Math.random() * 10)),
userAnswer: [],
outcome: [],
}
},
methods: {
checkAnswers() {
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === (this.randomNumberA[i] + this.randomNumberB[i])) {
this.outcome[i] = 'Correct';
} else {
this.outcome[i] = 'Incorrect';
}
}
}
}
}
</script>
You have some basic issues with your use of the template syntax here. According to the vue docs:
One restriction is that each binding can only contain one single
expression, so the following will NOT work: {{ var a = 1 }}
If you want to populate your arrays with random numbers you would be better calling a function on page mount. Something like this:
mounted() {
this.fillArrays()
},
methods: {
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
Then you can use template syntax to display your arrays.
It looks like you are setting up a challenge for the user to compare answers so I think you'd be better to have a function called on input: Something like:
<input type="whatever" v-model="givenAnswer[n-1]"> <button #click="processAnswer(givenAnswer[n-1])>Submit</button>
Then have a function to compare answers.
Edit
I have basically rewritten your whole page. Basically you should be using array.push() to insert elements into an array. If you look at this you'll see the randomNumber and answer arrays are populated on page mount, the userAnswer array as it is entered and then the outcome on button click.
<template>
<div>
<div >
<ul v-for="n in 5">
<li>
<p>{{ randomNumberA[n-1] }} + {{ randomNumberB[n-1] }} = </p>
<input class="input" type="text" maxlength="8" v-model.number="userAnswer[n-1]">
<p>{{ outcome[n-1] }}</p>
</li>
</ul>
</div>
<div class="button-container">
<button #click="checkAnswers" class="button">Submit Answers</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
randomNumberA: [],
randomNumberB: [],
answer: [],
userAnswer: [],
outcome: [],
}
},
mounted() {
this.fillArrays()
},
methods: {
checkAnswers() {
this.outcome.length = 0
for (var i = 0; i < 6; i++) {
if (this.userAnswer[i] === this.answer[i]) {
this.outcome.push('Correct');
} else {
this.outcome.push('Incorrect');
}
}
},
fillArrays() {
for (let i = 0; i < 5; i++) {
this.randomNumberA.push(Math.floor(Math.random() * 10))
this.randomNumberB.push(Math.floor(Math.random() * 10))
this.answer.push(this.randomNumberA[i] + this.randomNumberB[i])
}
}
}
}
</script>

create unique key in vue v-for loop

following some research across the web, i understand i should add index to the loop and then add it as a key.
how would you suggest creating a unique key for both td's in the following code:
<template v-for="lesson in lessons">
<td #click="sort(lesson.questions)" :key="lesson.lessonId">
questions
</td>
<td #click="sort(lesson.grade)" :key="lesson.lessonId">
grade
</td>
</template>
the only idea i had was to add index to the loop and then have the second index as follows:
:key="`${lesson.lessonId}+1`"
but that feels a bit odd and error prone, am i right?
There are 2 ways,
first is add the static number as you mentioned:
:key="`${lesson.lessonId}567`"
Second is generate a new ID, and you will using uuid version 4 package, that will generate random id for you,
<template>
:key="generateID"
</template>
<script>
const uuidv4 = require('uuid/v4');
module.exports = {
data: function () {
return {
generateID: uuidv4();
}
}
}
</script>
The special attribute 'key' can be either 'numeric' or 'string', to solve the problem you can prefix your lessonId with a string
<template v-for="lesson in lessons">
<td #click="sort(lesson.questions)" :key="`question_${lesson.lessonId}`">
questions
</td>
<td #click="sort(lesson.grade)" :key="`grade_${lesson.lessonId}`">
grade
</td>
</template>`
<ul id="example-2">
<li v-for="(item, index) in items" :key="key(index, item)">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
},
methods: {
key (index, item) {
return `${index}${item.message}`
}
}
})

Vuejs2- How to call a filter function from a method

I am using "moneyFormat" filter for formatting the currency value. It's formatting the values which is defined already. I want to format the dynamic values. Hence I have called the filter function through a method called "displayValue", but I am getting error
and the given input field also not updated.
Here is my code :
<template>
<b-card>
<div class="panel-body" id="app">
<table class="table table-hover">
<thead>
<tr>
<th style="width: 20px;">No.</th>
<th style="width: 330px;">Description</th>
<th style="width: 130px;" class="text-right">Charges</th>
<th style="width: 130px;">Total</th>
<th style="width: 130px;"></th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in rows" :key="row.qty">
<td>
{{ index +1 }}
</td>
<td>
<select name="" id="" class="form-control" v-model="row.billChgDesc">
<option v-for="option in billChgDescOpt" v-bind:value="option.value"
:key="option.value"> {{ option.text }}
</option>
</select>
</td>
<td>
<input #input="displayValue" class="form-control text-right" type="text" v-model="row.charges" data-type="currency" v-validate="'required'" :name="'charges' + index">
<span v-show="vErrors.has('charges' + index)" class="is-danger">{{ vErrors.first('charges' + index) }}</span>
<td>
<input class="form-control text-right" :value="row.qty * row.charges | moneyFormat" number readonly />
<input type="hidden" :value="row.qty * row.charges * row.tax / 100" number/>
</td>
<td>
<button class="btn btn-primary btn-sm" #click="addRow(index)"><i class="fa fa-plus"></i></button>
<button class="btn btn-danger btn-sm" #click="removeRow(index)"><i class="fa fa-minus"></i></button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3" class="text-right">DELIVERY</td>
<td colspan="1" class="text-right"><input class="form-control text-right" v-model="delivery" number/></td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</b-card>
</template>
<script>
import Vue from 'vue'
import accounting from 'accounting'
export default {
filters:{
moneyFormat: function (val){
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
},
data: function () {
return {
billChgDescOpt: [
{ value: '', text: 'Select' },
{ value: 'M', text: 'Maintenance Fee'},
{ value: 'W', text: 'Water Charges'},
{ value: 'P', text: 'Penalty Fee'},
],
rows: [
{qty: 5, billChgDesc: '', charges: 55.20, tax: 10},
{qty: 19, billChgDesc: '', charges: 1255.20, tax: 20},
],
grandtotal: 0,
delivery: 40
}
},
computed: {
total: function () {
var t = 0;
$.each(this.rows, function (i, e) {
t += accounting.unformat(e.total, ",");
});
return t;
},
taxtotal: function () {
var tt = 0;
$.each(this.rows, function (i, e) {
tt += accounting.unformat(e.tax_amount, ",");
});
return tt;
}
},
methods: {
addRow: function (index) {
try {
this.rows.splice(index + 1, 0, {});
} catch(e)
{
console.log(e);
}
},
removeRow: function (index) {
this.rows.splice(index, 1);
},
displayValue:function (e) {
var value = e.target.value
var a = this.filters.moneyFormat(value);
return a;
}
}
}
</script>
<style lang="scss" scoped>
.is-danger{
color: RED;
}
</style>
You could use:
this.$options.filters.moneyFormat(value)
Check: https://v2.vuejs.org/v2/api/#vm-options
For global filters, first set:
Vue.prototype.$filters = Vue.options.filters
And then:
this.$filters.foo
Edit:
Looking closer your code, you are not using the filter as a Vue filter and only calling from one point (a method) instead of calling inline from HTML, maybe it's better that the method itself returns the value of the input, like:
displayValue: function (e) {
var val = e.target.value
if (val > 0) {
return accounting.formatMoney(val, " ₹ ", 2, ",", ".");
}
}
Did it work? Or the same error is shown? If yes, can you paste the error?
Hope it helps!
As it's been said, if you want to use the filter, you need to do this.$options.filters.moneyFormat(value)
What you're trying to achieve it's rendered the moneyFormat inside an input and the value displayed is the v-model. It's this one you have to format.
So you can initialize a new data property filled with each row.charges formatted on mounted:
data: function () {
return {
rows: [
//...
],
currentCharges: []
}
},
mounted() {
this.rows.forEach(row => {
let formattedCharges = this.$options.filters.moneyFormat(row.charges)
this.currentCharges.push(formattedCharges)
})
},
and use this data to fulfill your inputs:
<tr v-for="(row, index) in rows">
<td>
<input v-model="currentCharges[index]" #input="displayValue($event, index)">
<td>
To keep the current row.charges updated, reassign it when the v-model updates:
methods: {
displayValue:function (e, index) {
// the target value is a string like this " ₹ 55.20"
// split it and convert the last element to Float type
let arrValue = e.target.value.split(" ")
let parseValue = parseFloat(arrValue[arrValue.length -1])
// reassign the row.charges with the new float value
this.rows[index].charges = parseValue
}
},

VUE filters through array

I recently started out with vuejs and the idea is to make a POS system with it. So currently I have an array of products and display them etc that all works just fine. Now my problem is when trying to calculate the sub-total price (price without the added VAT) of all products together.
So here is what I currently have:
<div class="col-md-12 productsList">
<div class="product" v-for="product in products">
<p>
<span class="col-md-4">#{{ product.name }}</span>
<span class="col-md-3 col-md-offset-1">#{{ product.amount }}</span>
<span class="col-md-3 col-md-offset-1" v-text="(parseFloat(product.price) * product.amount) | float"></span>
</p>
</div>
</div>
<script>
new Vue({
el: '.container-custom',
data: {
products: []
},
ready: function() {
var self = this;
},
methods: {
fetchSingleProduct: function(productId) {
this.$http.get('get/products/'+productId).then((product) => {
var existingProducts = this.products.filter(function (item) { return (item.id == product.data.id)});
if (existingProducts.length) {
existingProducts[0].amount += 1;
} else {
product.data.amount = 1;
this.products.push(product.data);
}
});
},
},
filters: {
float: function (value) {
return value.toFixed(2);
}
}
});
Each product holds:
name, amount, price, vat
Each product's vat can be different too (some products have 6%, some 21%) etc. How would I accomplish such thing with vue?
Just add all products that you need to sum into array and then create a computed property that returns the sum of that array with either array.reduce or a simple for loop ...