Nicole
Nicole

Reputation: 61

Update Vue subtraction method on input change

I have a Vue method that subtracts expenses from income to derive disposable income.

Right now, the subtraction method fires on blur, but it should constantly fire while both the income or expenses value are updated. For example, while someone types their income into the income input, the disposable income span that models the subtraction method should update as each digit is typed. Similarly, if someone types any expense into one of the expense components, disposable income should update while the user is typing.

    var budgetLine = Vue.extend({
        template: `
                <div>
                    <div class="row" v-for="item in items">
                        <input type="text" placeholder="Item"></input>
                        <input type="text" placeholder="How much?" v-model="item.qty"></input>
                        <button @click="addItem">+</button>
                        <button @click="removeItem">-</button>
                    </div>
                    <p id="result"><strong>Total:</strong> $ {{ totalQty }} </p>
                </div>
        `,

        data: function() {
        return {
            items: []
        };
        },
        watch: {
        totalQty(value) {
            this.$emit('update-expense', value)
        }
        },
        computed: {
        totalQty() {
            return this.items.reduce((total, item) => {
            return total + Number(item.qty);
            }, 0);
        },
        },

        methods: {
        addItem() {
            this.items.push({
            qty: 0
            });
        },
        removeItem(item) {
            this.items.pop(item);
        }
        },

        mounted() {
        this.addItem()
        }
    });

    var budgetApp = new Vue({
        el: '#app',
        data: {
        budgets: {
            'One': 0,
            'Two': 0,
            'Three': 0
        },
        form: {
            income: 0,
            expenses: 0,
            dispIncome: 0
        }
        },
        components: {
        'budget-line': budgetLine
        },
        watch: {
        budgets: {
            deep: true,
            handler() {
            this.form.expenses = this.budgetKeys.reduce((accum, key) => accum + this.budgets[key], 0)
            }
        }
        },
        computed: {
            budgetKeys() {
                return Object.keys(this.budgets)
            },
        },
        methods: {
            updateIncome(event) {
                this.form.income = event.target.value;
                this.form.dispIncome = this.form.income - this.form.expenses
            },
            updateExpenses(event) {
                this.form.expenses = event.target.value;
                this.form.dispIncome = this.form.income - this.form.expenses
            },
            calculateExpense(amount, budget) {
                this.budgets[budget] = amount;
            }
        }
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
        <header>
            <div class="header_container">  
                <div class="header_container-copy">  
                    <h1>Let's talk budget.</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
                </div>
                <div class="header_container-results">  
                    <h2>Disposable Income</h2>
                    <div class="row"><span>Income:</span><span>{{ form.income }}</span></div>
                    <div class="row"><span>Expenses:</span><span>{{ form.expenses }}</span></div>
                    <div class="row"><span>Disposable Income:</span><span>{{ form.dispIncome }}</span></div>
                </div>
            </div>
        </header>
        <h2>Income</h2>
        <input v-model="form.income" @change="updateIncome" type="number" class="form-control" name="income" id="income" placeholder="Income">
        <h2>Expenses</h2>
        <div class="budget" v-for="budget in budgetKeys">
            <h3>{{budget}}</h3>
            <budget-line v-on:update-expense="calculateExpense($event, budget)"></budget-line>
        </div>
    </div>

Upvotes: 0

Views: 1217

Answers (2)

Yom T.
Yom T.

Reputation: 9200

You're almost there, just need to watch for changes of this form.income model instead of updating the value by method.

Working demo

var budgetLine = Vue.extend({
    template: `
      <div>
        <div class="row" v-for="item in items">
          <input type="text" placeholder="Item"></input>
          <input type="text" placeholder="How much?" v-model="item.qty"></input>
          <button @click="addItem">+</button>
          <button @click="removeItem">-</button>
        </div>
        <p id="result"><strong>Total:</strong> $ {{ totalQty }} </p>
      </div>
    `, 

    data: function () {
      return {
        items: []
      };
    },
    watch: {
      totalQty(value) {
        this.$emit('update-expense', value)
      }
    },
    computed: {
      totalQty() {
        return this.items.reduce((total, item) => {
          return total + Number(item.qty);
        }, 0);
      },
    },

    methods: {
      addItem() {
        this.items.push({
          qty: 0
        });
      },
      removeItem(item) {
        this.items.pop(item);
      }
    },

    mounted() {
      this.addItem()
    }
  });

var budgetApp = new Vue({
    el: '#app',
    data: {
      budgets: {
        'One': 0,
        'Two': 0,
        'Three': 0
      },
      form: {
        income: 0,
        expenses: 0,
        dispIncome: 0
      }
    },
    components: {
      'budget-line': budgetLine
    },
    watch: {
      budgets: {
        deep: true,
        handler(budget) {
          this.form.expenses = this.budgetKeys.reduce((accum, key) => accum + this.budgets[key], 0);
        }
      },
      
      'form.income'() {
        this.updateIncome();
      }
    },
    computed: {
      budgetKeys() {
        return Object.keys(this.budgets)
      },
    },
    methods: {
      updateIncome() {
        this.form.dispIncome = this.form.income - this.form.expenses;
      },
      calculateExpense(amount, budget) {
        this.budgets[budget] = this.form.expenses = amount;
        this.updateIncome();
      }
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <header>
    <div class="header_container">
      <div class="header_container-copy">
        <h1>Let's talk budget.</h1>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
          dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
      </div>
      <div class="header_container-results">
        <h2>Disposable Income</h2>
        <div class="row"><span>Income:</span> <span>{{ form.income }}</span></div>
        <div class="row"><span>Expenses:</span> <span>{{ form.expenses }}</span></div>
        <div class="row"><span>Disposable Income:</span> <span>{{ form.dispIncome }}</span></div>
      </div>
    </div>
  </header>
  <h2>Income</h2>
  <input v-model="form.income" type="number" class="form-control" name="income" id="income" placeholder="Income">
  <h2>Expenses</h2>
  <div class="budget" v-for="budget in budgetKeys">
    <h3>{{budget}}</h3>
    <budget-line v-on:update-expense="calculateExpense($event, budget)"></budget-line>
  </div>
</div>

Upvotes: 1

eborrallo
eborrallo

Reputation: 750

first i think that the values ( Income, Expenses, Disposable Income) span will must be a computed property no a data , since they are values that are being calculated and rendered. Anyway, as you already have it if you want a quick solution so that it works, you should implement :

v-on:keyup="updateIncome"

into your input v-model="form.income" near the @change function

var budgetLine = Vue.extend({
        template: `
                <div>
                    <div class="row" v-for="item in items">
                        <input type="text" placeholder="Item"></input>
                        <input type="text" placeholder="How much?" v-model="item.qty"></input>
                        <button @click="addItem">+</button>
                        <button @click="removeItem">-</button>
                    </div>
                    <p id="result"><strong>Total:</strong> $ {{ totalQty }} </p>
                </div>
        `,

        data: function() {
        return {
            items: []
        };
        },
        watch: {
        totalQty(value) {
            this.$emit('update-expense', value)
        }
        },
        computed: {
        totalQty() {
            return this.items.reduce((total, item) => {
            return total + Number(item.qty);
            }, 0);
        },
        },

        methods: {
        addItem() {
            this.items.push({
            qty: 0
            });
        },
        removeItem(item) {
            this.items.pop(item);
        }
        },

        mounted() {
        this.addItem()
        }
    });

    var budgetApp = new Vue({
        el: '#app',
        data: {
        budgets: {
            'One': 0,
            'Two': 0,
            'Three': 0
        },
        form: {
            income: 0,
            expenses: 0,
            dispIncome: 0
        }
        },
        components: {
        'budget-line': budgetLine
        },
        watch: {
        budgets: {
            deep: true,
            handler() {
            this.form.expenses = this.budgetKeys.reduce((accum, key) => accum + this.budgets[key], 0)
            }
        }
        },
        computed: {
            budgetKeys() {
                return Object.keys(this.budgets)
            },
        },
        methods: {
            updateIncome(event) {
                this.form.income = event.target.value;
                this.form.dispIncome = this.form.income - this.form.expenses
            },
            updateExpenses(event) {
                this.form.expenses = event.target.value;
                this.form.dispIncome = this.form.income - this.form.expenses
            },
            calculateExpense(amount, budget) {
                this.budgets[budget] = amount;
            }
        }
    });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
        <header>
            <div class="header_container">  
                <div class="header_container-copy">  
                    <h1>Let's talk budget.</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
                </div>
                <div class="header_container-results">  
                    <h2>Disposable Income</h2>
                    <div class="row"><span>Income:</span><span>{{ form.income }}</span></div>
                    <div class="row"><span>Expenses:</span><span>{{ form.expenses }}</span></div>
                    <div class="row"><span>Disposable Income:</span><span>{{ form.dispIncome }}</span></div>
                </div>
            </div>
        </header>
        <h2>Income</h2>
        <input v-model="form.income" v-on:keyup="updateIncome" @change="updateIncome" type="number" class="form-control" name="income" id="income" placeholder="Income">
        <h2>Expenses</h2>
        <div class="budget" v-for="budget in budgetKeys">
            <h3>{{budget}}</h3>
            <budget-line v-on:update-expense="calculateExpense($event, budget)"></budget-line>
        </div>
    </div>

Upvotes: 0

Related Questions