Vue.js How to calculate totals? - vue.js

How can I calculate the total amount from an array?
I pass data to the child component as prop, and I am stuck here. When I console log prop, it returns a very complicated object . I tried this.values.reduce() function but it does not work.
<template>
<tr v-for="value in values" >
<th scope="row">{{$index+1}}</th>
<td>{{value.name}}</td>
<td>-</td>
<td>${{value.total}}</td>
</tr>
<tr>
<th></th>
<td><strong>Total:{{total}}</strong></td>
<td>-</td>
<td>-</td>
</tr>
</template>
<script>
export default {
props: ['values'],
ready: function() {
}
}
</script>

In case anyone else is in the same situation as me I thought I'd add this answer. I needed to get the values from nested objects then push them to an array before reducing them:
total: function(){
let total = [];
Object.entries(this.orders).forEach(([key, val]) => {
total.push(val.price) // the value of the current key.
});
return total.reduce(function(total, num){ return total + num }, 0);
}
This uses ES7 .entries to loop through the object which looked like this:
orders = {
1: {title: 'Google Pixel', price: 3000},
2: {title: 'Samsung Galaxy S8', price: 2500},
3: {title: 'iPhone 7', price: 5000}
}
You can then display the total in your template with:
<span> {{total}} </span>

var payments = new Vue({
el: "#payments",
data: {
payments: [
{ name: "houseRent", amount: 1000, is_paid: true },
{ name: "houseRent", amount: 1500, is_paid: true },
{ name: "houseRent", amount: 1200, is_paid: false },
{ name: "houseRent", amount: 1070, is_paid: true },
{ name: "houseRent", amount: 1040, is_paid: false }
]
},
computed: {
totalAmount: function () {
var sum = 0;
this.payments.forEach(e => {
sum += e.amount;
});
return sum
}
}
});`

As you proposed, you could use the Array#reduce function. Starting from this example on SO, you could adapt it to your needs and only add value.total to the sumtotal.
To compute the total of all values, you can use computed properties, which will display as {{ total }} in your template:
<script>
export default {
props: {
values: {
type: Array,
default: []
},
}
ready: function() {
},
computed: {
total: function() {
if (!this.values) {
return 0;
}
return this.values.reduce(function (total, value) {
return total + Number(value.total);
}, 0);
}
}
}
</script>
Note: This will of course only work, if value.total is a unitless number (e.g. 1, not '1 USD'). Otherwise you would need to strip the in the reduce function as well.

Related

Data Object unable to summation inside the computed properties in VUE js 3

Let see the following code segment in vue.js 3,
<template>
<div>
<h2>Computed Total - {{getPrice()}}</h2>
</div>
</template>
<script>
export default {
name: "ComputedProperties",
data(){
return{
items: [
{
id: 1,
title: 'TV',
price: 100,
},
{
id: 2,
title: 'Phone',
price: 200,
},
{
id: 3,
title: 'Laptop',
price: 300
}
]
}
},
computed: {
getPrice(){
return items.reduce((total, curr) => (total = total + curr.price), 0)
}
}
}
</script>
After run this code it is showing, error 'items' is not defined no-undef
As we know that computed property doesn't need to declarer any argument. Then what's wrong with the code?
There are few problems with your code:
When using computed property, don't use () - it is a property, not a function
<h2>Computed Total - {{ getPrice }}</h2>
All members of data, computed or methods when used in the script part of the Vue component, must be referenced with this ...for example this.items
computed: {
getPrice() {
return this.items.reduce((total, curr) => total + curr.price, 0)
}
}

Vue data, computed, and methods

Wondering if I should directly update a potentially large array in data() using a method, or instead, have the method set another primitive property in data() and use computed to return an altered array using that? Examples of both approaches below:
Approach #1:
data() {
return {
users: [
{
name: 'alice'
selected: true
},
name: 'bob'
selected: false
// ...
methods: {
handleSelection(selectedIndex) {
this.users = this.users.map((item, index) => {
item.selected = selectedIndex === index ? true : false;
return item;
});
}
Approach #2:
data() {
return {
selectedIndex: 0,
users: [
{
name: 'alice'
selected: true
},
name: 'bob'
selected: false
// ...
computed: {
usersSelected() {
return this.users.map((item, index) => {
item.selected = this.selectedIndex === index ? true : false;
return item;
});
}
//...
methods: {
handleSelection(selectedIndex) {
this.selectedIndex = selectedIndex;
}
Is one better than the other or any additional suggestions/improvements? Thanks!
You should use a computed property. That way, you don't need to care about remembering to call various methods, to keep data in sync.
Your computed property can be made a bit more simple, with your requirements.
new Vue({
el: '#app',
data: {
selectedUserIndex: null,
users: [{
name: 'Mark',
id: 1
},
{
name: 'John',
id: 2
},
{
name: 'Evan',
id: 3
}
]
},
computed: {
selectedUser() {
return this.selectedUserIndex >= 0
? this.users[this.selectedUserIndex]
: null
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>
Selected user:
<span v-if="selectedUser">{{selectedUser.name}}</span>
<em v-else>No user selected</em>
</p>
<ul>
<li #click="selectedUserIndex = index" v-for="(user, index) in users" :key="user.id">
{{user.name}}
</li>
</ul>
</div>

Vue - Deep watch change of array of objects, either if a object is added of modified

I'm trying to create an element in vue.js, so that when I update my cart it will show a warning with the item added/updated to cart. So if I add a new car, it would show that last car added.
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
to
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3},
{ name: 'Mustang', quantity: 1}
]
will show
<div>
You have 1 x Mustang in Cart
</div>
But if I update the quantity of a car that was already in the cart, it will show that last car updated.
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
to
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 4}
]
will show
<div>
You have 4 x Toyota in Cart
</div>
So far I made it work based in this answer
new Vue({
el: '#app',
data: {
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
}
});
Vue.component('car-component', {
props: ["car"],
data: function() {
return {
lastAdded:''
}
},
template: `
<div>
You have {{lastAdded.quantity}} x {{lastAdded.name}} in Cart
</div>`,
watch: {
car: {
handler: function(newValue) {
this.lastAdded = newValue;
},
deep: true
}
}
});
html
<script src="https://unpkg.com/vue#2.5.17/dist/vue.js"></script>
<body>
<div id="app">
<p>Added to Cart:</p>
<car-component :car="car" v-for="car in cars"></car-component>
</div>
</body>
The point is that now it just detects when a object is already in the cart and changes quantity, but not when there is a new car added. I tried to play with another watcher, but it didn't work. Thanks in advance!
hmm how would I do this?
seems to me we have an array of objects and we are tracking the most recently added or modified object. Sure.
So, I think I'd want to only track the recently modified object and render that.
first the html:
<div id="app">
<p>Added to Cart:</p>
<car-component :car="latestCar"></car-component>
</div>
and the vue instance:
new Vue({
el: '#app',
data: {
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
],
latestCar: {}
},
methods: {
updateLatestCar(car) {
this.latestCar = car;
//call this method from any other method where updates take place
//so if would be called from your addCar method and your updateCar method
//(which I assume exist even though they are not shown in your code)
}
}
});
Vue.component('car-component', {
props: ["car"],
data: function() {
return {
lastAdded:''
}
},
template: `
<div>
You have {{lastAdded.quantity}} x {{lastAdded.name}} in Cart
</div>`,
watch: {
car: {
handler: function(newValue) {
this.lastAdded = newValue;
},
deep: true
}
}
});
If you are modifying your array of objects via some method that is external to the Vue instance then that will require some additional thought.
But it seems like for this you'd have some methods in the Vue instance methods block like this:
addCar(car) {
this.cars.push(car);
this.updateLatestCar(car);
},
updateCar(index, car) {
this.cars[index] = car;
this.updateLatestCar(car);
}
You could pass the entire cars[] array to <car-component>, and allow the component to determine which element of cars[] to display a message about:
In car-component, add a prop (typed for safety) to hold the passed-in cars[]:
Vue.component('car-component', {
// ...
props: {
cars: Array
},
}
Add two data properties:
* `car` - the current car.
* `copyOfCars` - the last known copy of `cars[]`, used to determine which array element has changed. *Note: While watchers are provided both the old and new values of the watched property, the old value does not actually indicate the previous value for arrays of objects.*
Vue.component('car-component', {
//...
data() {
return {
car: {},
copyOfCars: undefined, // `undefined` because we don't need it to be reactive
};
},
}
Define a method (e.g., named findActiveCar) that determines which element in a given cars[] is most recently "active" (newly added or modified).
Vue.component('car-component', {
// ...
methods: {
/**
* Gets the newest/modified car from the given cars
*/
findActiveCar(newCars) {
if (!newCars || newCars.length === 0) return {};
let oldCars = this.copyOfCars;
// Assume the last item of `newCars` is the most recently active
let car = newCars[newCars.length - 1];
// Search `newCars` for a car that doesn't match its last copy in `oldCars`
if (oldCars) {
for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) {
if (newCars[i].name !== oldCars[i].name
|| newCars[i].quantity !== oldCars[i].quantity) {
car = newCars[i];
break;
}
}
}
this.copyOfCars = JSON.parse(JSON.stringify(newCars));
return car;
}
}
}
Define a watcher on the cars property that sets car to the new/modified item from findActiveCar().
Vue.component('car-component', {
// ...
watch: {
cars: {
handler(newCars) {
this.car = this.findActiveCar(newCars);
},
deep: true, // watch subproperties of array elements
immediate: true, // run watcher immediately on this.cars[]
}
},
}
Vue.component('car-component', {
props: {
cars: Array,
},
data() {
return {
car: {},
copyOfCars: undefined,
}
},
template: `<div>You have {{car.quantity}} x {{car.name}} in Cart</div>`,
watch: {
cars: {
handler(newCars) {
this.car = this.findActiveCar(newCars);
},
deep: true,
immediate: true,
}
},
methods: {
findActiveCar(newCars) {
if (!newCars || newCars.length === 0) return {};
let oldCars = this.copyOfCars;
let car = newCars[newCars.length - 1];
if (oldCars) {
for (let i = 0; i < Math.min(newCars.length, oldCars.length); i++) {
if (newCars[i].name !== oldCars[i].name
|| newCars[i].quantity !== oldCars[i].quantity) {
car = newCars[i];
break;
}
}
}
this.copyOfCars = JSON.parse(JSON.stringify(newCars));
return car;
}
}
});
new Vue({
el: '#app',
data: () => ({
cars: [
{ name: 'Porsche', quantity: 2},
{ name: 'Ferrari', quantity: 1},
{ name: 'Toyota', quantity: 3}
]
}),
methods: {
addCar() {
this.cars.push({
name: 'Mustang', quantity: 1
})
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<h1>Added to Cart</h1>
<button #click="addCar">Add car</button>
<ul>
<li v-for="(car, index) in cars" :key="car.name + index">
<span>{{car.name}} ({{car.quantity}})</span>
<button #click="car.quantity++">+</button>
</li>
</ul>
<car-component :cars="cars" />
</div>

vuejs start a timer for every record

I have this component rendered, I need to tell the users how much time remaining for each record displayed
I couldn't get anything worked out, I don't know any legible ways to work it out, so just trying calling out methods. I tried child components too but no avail.
<template>
<div>
<ul v-for="question in questions">
{{init_timer(secs)}}
{{start_timer()}}
<li>
{{question.name}}
<span class="question_clock">Validity : -- counter : {{time_remaining}}
</span> </small>
</li>
</ul>
</div>
</template>
<script>
export default {
data: function(){
return {
questions: [],
time_remaining : 1
};
},
methods: {
init_timer: function(secs) {
this.time_remaining = secs
},
start_timer: function() {
console.log('startiem')
setTimeout(function () { this.time_remaining -= 1 }.bind(this), 1000)
},
}
,
created: function(){
$.getJSON('/questions/json', function(response){
this.questions = response
}.bind(this ));
},
}
</script>
any help is much appreciated
I wrote a quick timer component that might accomplish what you want.
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
Basically, you set the initial property which is the time allowed in seconds and the timer will update the DOM every interval seconds.
Here is an example.
console.clear()
const questions = [{
name: "What is your name?",
time: 30
},
{
name: "What is your quest?",
time: 50
},
{
name: "What is your favorite color?",
time: 20
},
{
name: "What is the airspeed velocity of an unladen swallow?",
time: 10
},
]
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
new Vue({
el: "#app",
data: function() {
return {
questions: [],
time_remaining: 1
};
},
created: function() {
setTimeout(() => this.questions = questions, 100)
// $.getJSON('/questions/json', function(response){
// this.questions = response
// }.bind(this ));
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="question in questions">
{{question.name}}
<span class="question_clock">
<timer :initial="question.time"></timer>
</span>
</li>
</ul>
</div>

The use of bidirectional binding of components in Vue.js

I'm a new learner of Vue.js and trying to implement the example (example of currency filter) on the official guideline.
However, when implementing, I rename the property of the component (value) to (priceValue). After the change, the input box cannot format the value - it always shows '0' instead of the formatted value.
It is the only change I made. What is the problem?
Vue.component('currency-input', {
template: '\
<div>\
<label v-if="label">{{ label }}</label>\
$\
<input\
ref="input"\
v-bind:value="priceValue"\
v-on:input="updateValue($event.target.value)"\
v-on:focus="selectAll"\
v-on:blur="formatValue"\
>\
</div>\
',
props: {
priceValue: {
type: Number,
default: 0
},
label: {
type: String,
default: ''
}
},
mounted: function () {
this.formatValue()
},
methods: {
updateValue: function (value) {
var result = currencyValidator.parse(value, this.priceValue)
if (result.warning) {
this.$refs.input.value = result.value
}
this.$emit('input', result.value)
},
formatValue: function () {
// console log here always get 0
this.$refs.input.value = currencyValidator.format(this.priceValue)
},
selectAll: function (event) {
setTimeout(function () {
event.target.select()
}, 0)
}
}
})
new Vue({
el: '#app',
data: {
price: 0,
shipping: 0,
handling: 0,
discount: 0
},
computed: {
total: function () {
return ((
this.price * 100 +
this.shipping * 100 +
this.handling * 100 -
this.discount * 100
) / 100).toFixed(2)
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.rawgit.com/chrisvfritz/5f0a639590d6e648933416f90ba7ae4e/raw/98739fb8ac6779cb2da11aaa9ab6032e52f3be00/currency-validator.js"></script>
<div id="app">
<currency-input
label="Price"
v-model="price"
></currency-input>
<currency-input
label="Shipping"
v-model="shipping"
></currency-input>
<currency-input
label="Handling"
v-model="handling"
></currency-input>
<currency-input
label="Discount"
v-model="discount"
></currency-input>
<p>Total: ${{ total }}</p>
</div>
According to DOCS:
For a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
This can be configured sinse 2.2.x with a model options block:
Vue.component('currency-input', {
model: {
prop: 'propValue',
// event: 'input' - you can also customize event name, not needed in your case
},
With this in place, your code will work again: https://jsfiddle.net/wostex/j3a616a5/