Advent of JavaScript, Day 7
Challenge #7 is creating a Tip Calculator that updates a dollar amount:
With the project files downloaded and codesandbox
’d into a live CodeSandbox, I’m ready to get going!
User Requirements
calculate tip based on tip percentage, bill amount, and number of people
When reviewing the included index.html
, it looks like the data exists for wiring:
- Tip –
<span id="tip-amount">
- Bill Amount –
<span id="total-per-person">
- Number of People –
<input id="number-of-people">
- Tip % –
<input name="tip">
I could normally get a value via form[name]
, but since tip
isn’t wrapped in a <form>
this won’t work.
Instead, I can use CSS
’ :checked
property to find the selected tip amount:
document.querySelector('input[name=tip]:checked').value
Wiring it Up
I could use Vanilla JS and listen to events on each input, but I’d like to learn how Vue handles Radio inputs.
-
Add Vue, again
<script src="https://unpkg.com/vue@next"></script>
-
Scaffold my
TipCalculator
:const TipCalculator = { data() { return {} }, } Vue.createApp(TipCalculator).mount(document.querySelector('.wrapper'))
I can do this from memory now 🤩
-
Mapping each
<input>
to av-model
:Apparently this will automatically map it to the
data()
object.I did change the
value
s fortip
from:<input type="radio" name="tip" value="15%" />
to:
<input type="radio" name="tip" value="0.05" v-model="tipPercentage" />
-
Creating
computed
properties fortipAmount
andtotalPerPerson
:JavaScript is notorious for erroneous floating-point arithmetic.
The easiest way I’ve found to deal with currency is to simply multiply by 100 so that we’re dealing with whole numbers.
Then, when dividing, use
Math.ceil
so that we don’t underpay our bill (or tip!).(number / 100).toFixed(2)
will round back to two decimal places.
Finishing Up
This ended up being a very small app, but with some “gotchas”:
const roundUp = (number) => {
return Number((Math.ceil(number * 100) / 100).toFixed(2))
}
const TipCalculator = {
data() {
return {
billAmount: '102.02',
numberOfPeople: '3',
tipPercentage: '0.15',
}
},
computed: {
tipAmount() {
return roundUp(Number(this.billAmount) * Number(this.tipPercentage))
},
totalPerPerson() {
return roundUp(
(Number(this.billAmount) + this.tipAmount) / Number(this.numberOfPeople)
)
},
},
}
See all the casting to Number
? If I RTFM, I would’ve seen v-model.number
would cast it automatically:
With that updated, I do far less casting:
const roundUp = (number) => {
return Number((Math.ceil(number * 100) / 100).toFixed(2))
}
const TipCalculator = {
data() {
return {
billAmount: 102.02,
numberOfPeople: 3,
tipPercentage: 0.15,
}
},
computed: {
tipAmount() {
return roundUp(this.billAmount * this.tipPercentage)
},
totalPerPerson() {
return roundUp((this.billAmount + this.tipAmount) / this.numberOfPeople)
},
},
}