Part 8: Fix bugs with number-to-string coercion [Beginner Tutorial: Learn Vue, CSS Grid and Flexbox]
Before you continue, make sure you see this in Codepen
What we will accomplish
By the end of Part 8, our calculator should be fully functional.
The problem I’ve been avoiding until now
In any digital calculator, the expected behavior is as follows:
- Initially, the number you see is
0
- After entering a single number, the
0
is replaced by that number - Entering subsequent numbers builds up a long string of number characters that you entered before applying any of the operators (+, -, *, /)
- Applying an operator should capture a number, not a string, to calculate the solution to a numerical equation
All this is to say, we have to manually convert the value stored in our data model's current
property between a number
and a string
depending on a few different circumstances.
Let’s start by updating a Vue directive in our template
Step 1: Use a ternary operator to conditionally set a value in our data model
- Find the
button
element whose text content is the interpolation of the aliased propertydigit
.
The expression used in its v-on
directive currently assigns digit
to current
. This is no longer smart enough. We instead want to update current
to one of two possible values, based on a specific condition. Usually, we would use normal control flow via if() ... else
to accomplish this. However, in a Vue directive, that syntax is not valid. Instead, we can and must use JavaScript’s ternary operator
. The syntax is as follows:
condition ? true : false
Here’s an example:
1 + 1 == 2 ? 'Yes' : 'No'
If 1 + 1
is equal to 2
then Yes
is returned. Otherwise, No
is returned.
The answer is a bit difficult to describe in words, so here it is:
current === 0 ? current = String(digit) : current += digit
What is this doing?
Each time any of the buttons labeled 0–9 is clicked:
- It asks: ‘is the value stored in
current
right now exactly equal to0
? - If it is: re-assign the value stored in
current
to thestring
-representation of the value referenced bydigit
(which is the same as the label of the button). At this point,current
is of typestring
, notnumber
. - If it is not exactly equal to
0
: re-assigncurrent
to the result of concatenating the value referenced bydigit
with the value currently stored incurrent
.
For example:
- When
current
is0
, pressing7
will changecurrent
to'7'
- When
current
is'7'
, pressing4
will changecurrent
to'74'
- When
current
is'74'
, pressing8
will changecurrent
to'748'
New problem
As you see in the example, as soon as you press a button to begin an equation, current
is no longer a number
but rather a string
. Well, back in our data model, the computed
function named answer
is currently configured to expect number
s to be stored in this.total
and this.current
. If any of them is a string
then answer
will return NaN
. That’s no good.
Step 2: Coerce a value from string to number before operating on it
- In the JS pane, find the line inside
answer
— the computed function — thatreturn
s the result of callingeval()
- We need to make one small but significant edit: amend the third part of
eval
‘s argument so that instead of concatenatingthis.current
in its current form (a string), we concatenate a number. The way to do this is by passingthis.current
as the sole argument to the native JavaScript function,Number()
.
Your code should now look like this
answer: function() { return eval(this.total + this.selected + Number(this.current));}
Step 3: Update the ‘%’ button’s Vue directive to correctly operate on a value of type number
- Back in your template in the HTML pane, in the
section
with aclass
ofmodifiers
, draw your attention to the thirdbutton
— the one whose label is a percent sign%
The way it is now, we are performing a math operation (multiplying by 0.01
) on what could be a value of type string
. Let’s fix this to guarantee we only ever operate on a number
.
- Instead of multiplying
current
by itself, multiply it by theNumber()
coerced version of itself — by passingcurrent
as an argument toNumber()
The result should look familiar now:
current = Number(current) * 0.01
Step 4: Use another ternary operator to correctly identify when to apply a decimal point
The problem
We should be able to insert a single decimal point into the current working number. But only one. And whenever we want.
- Find the last
button
in your template: it is in the lastsection
, is the second of two buttons, and its text content is a single decimal point
We’re about to write another seemingly long ternary operation
. Similar to earlier, it’s probably smarter if I write it below and walk you through what it does rather than try to describe it and hope you type it correctly. Here it is:
current.toString().indexOf('.') == -1 ? current += '.' : null
To explain
The part before the ?
- In a very complicated way, it asks: ‘Does
current
have a.
in it?’
The way it asks this is as follows:
- Look-up the value stored in
current
- Coerce that value to a string using JavaScript’s
toString()
function - Search the string for the existence of a decimal point
.
and return the index any such character is found at using JavaScript’sindexOf()
function indexOf()
will return one of two values: a positive number, or-1
. If it returns a positive number, it means that the string passed in as an argument was found — specifically at the index of that positive number. IfindexOf()
was unable to find the passed-in value in the searched string, it will return-1
.- We set our condition to specifically check whether
indexOf()
returns-1
.
The part between the ?
and the :
- If
indexOf()
does return-1
:current
does not yet contain a decimal point, so we are safe to add one by executing the code:current += '.'
The part after the :
- If
indexOf()
returns anything other than-1
:current
already contains a decimal point. We should not add another one. In fact, we should do nothing. That’s why I just returnnull
.
current.toString().indexOf('.') == -1 ? current += '.' : null
I re-pasted the code from above in case you are reading the last few bullets and don’t want to keep scrolling back up.
Try it out…it all works!
Play around, please. I hope all your hard work feels great.
If you think your application is broken on account of things not working as described above, click through to Part 9 to see a screenshot of my Codepen up to this point.
This marks the end of Part 8
In Part 9, we will make a few modifications to the template before proceeding to style our application.