The story so far
We're going to talk about two things:
- Function arguments
- Accumulating HTML
You can try the new app.
More coins
Let's change the pig, so it will take more types of coins:
Here's what we had for the original app:
There's one button for the penny. Clicking it (line 1) runs the function PiggyBank.addCoin()
.
The function (lines 6 to 11) adds a penny to the variable pigFortune
(line 7), stores the variable in localStorage
(lines 8 and 9), and injects the variable into the HTML (line 10).
Let's add code for the nickel. The button is easy enough. Copy the code for the penny, and adjust:
<button onclick="???"
type="button" class="btn btn-primary"
title="Put a penny in the pig"
>Penny</button>
<button onclick="???"
type="button" class="btn btn-primary"
title="Put a nickle in the pig"
>Nickle</button>
What about the onclick
code? This is what we had for the old pig:
<button onclick="PiggyBank.addCoin()" ...
...
PiggyBank.addCoin = function() {
pigFortune += 0.01;
localStorage.setItem("pigFortune", pigFortune.toString());
$("#amount").html(pigFortune);
};
For the improved pig, we could copy-and-paste the penny code for the nickel, like this:
<button onclick="BetterPiggyBank.addPenny()" ...
<button onclick="BetterPiggyBank.addNickel()" ...
...
BetterPiggyBank.addPenny = function() {
pigFortune += 0.01;
localStorage.setItem("pigFortune", pigFortune.toString());
$("#amount").html(pigFortune);
};
BetterPiggyBank.addNickel = function() {
pigFortune += 0.05;
localStorage.setItem("pigFortune", pigFortune.toString());
$("#amount").html(pigFortune);
};
Notice that this new app has a different namespace: BetterPiggyBank
.
The functions addPenny
and addNickel
are almost identical. The only difference is this:
pigFortune += 0.01;
...
pigFortune += 0.05;
That's one character that is different. The rest of the code is repeated.
Reusing code
Repeated code is a problem, in real web apps. You need to test it twice, and maybe debug it twice. If requirements change, you have to fix the code twice, and maybe add more bugs.
Let's think this through. Here's the code for a penny.
For a nickel, we only want to change one thing:
What if we could put the coin's value in a variable?
For a penny, set amount
to 0.01. For a nickel, set amount
to 0.05. Then the same code would work for both.
That's exactly what we'll do.
Function arguments
Let's change the code to this:
<button onclick="BetterPiggyBank.addCoin(0.01)" ...
<button onclick="BetterPiggyBank.addCoin(0.05)" ...
...
BetterPiggyBank.addCoin = function(amount) {
pigFortune += amount;
localStorage.setItem("pigFortune",
pigFortune.toString());
$("#amount").html(pigFortune);
};
We make one function, BetterPiggyBank.addCoin()
. It takes an argument, also called a parameter.
BetterPiggyBank.addCoin = function(amount) {
When you call BetterPiggyBank.addCoin()
, you give it a value for amount
:
<button onclick="BetterPiggyBank.addCoin(0.01)" ...
<button onclick="BetterPiggyBank.addCoin(0.05)" ...
Given this:
onclick="BetterPiggyBank.addCoin(0.01)" ...
...
BetterPiggyBank.addCoin = function(amount) {
amount
would have the value 0.01 for that run of the function.
With this:
onclick="BetterPiggyBank.addCoin(0.05)" ...
...
BetterPiggyBank.addCoin = function(amount) {
amount
would have the value 0.05 for that run of the function.
So amount
is temporarily replaced by the parameter's value in the call.
One function works for both pennies and nickels.
Add dimes and quarters
Adding dimes and quarters is easy. We don't need more functions. Just add buttons, and change the calls to the the function.
<button onclick="BetterPiggyBank.addCoin(0.01)" ...
<button onclick="BetterPiggyBank.addCoin(0.05)" ...
<button onclick="BetterPiggyBank.addCoin(0.10)" ...
<button onclick="BetterPiggyBank.addCoin(0.25)" ...
Woohoo!
Rounding
The pig has a problem. Here's what I saw when I added a penny and a nickel:
The problem is that computers and people use different number systems. What is round in one system can have extra decimals in the other.
Earlier, we used Math.round()
, but it only rounds to whole numbers. We want to round to the nearest cent.
JS doesn't have a function for that.
Google to the rescue! A page on the Mozilla Development Network gives this:
function precisionRound(number, precision) {
var factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
Give it a number to round, in the argument number
, and the number of decimal places, in precision
. It sends back the rounded number.
So, precisionRound(2.345678, 2)
would send back 2.35
. Nice!
Let's add the function to our pig. Let's change the syntax a little, so that the function lives in the new namespace:
BetterPiggyBank.precisionRound = function (number, precision) {
var factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
};
This one has two arguments. You can have an many as you want.
How to use the function?
Line 3 is new. Let's break it down.
BetterPiggyBank.precisionRound(pigFortune, 2);
...
BetterPiggyBank.precisionRound = function (number, precision) {
For this run of the function precisionRound
, number
is replaced by the value in pigFortune
. precision
is replaced by 2.
The arguments send data into precisionRound
. But we want precisionRound
to send back the rounded number.
The return
statement does that:
pigFortune = BetterPiggyBank.precisionRound(pigFortune, 2);
...
BetterPiggyBank.precisionRound = function (number, precision) {
var factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
};
So, BetterPiggyBank.precisionRound(pigFortune, 2)
send two values into precisionRound
. The return
statement… well, returns a value back.
What do we do with the value?
pigFortune = BetterPiggyBank.precisionRound(pigFortune, 2);
Put it into pigFortune
, overwriting whatever was there. So if pigFortune
has a strange value like 0.06000000005, then…
pigFortune = BetterPiggyBank.precisionRound(pigFortune, 2);
… would turn it into 0.06. Then, this…
localStorage.setItem("pigFortune", pigFortune.toString());
$("#amount").html(pigFortune);
… will store the new round value in localStorage
, and inject it into the HTML.
You use functions a lot in JavaScript. Arguments let functions be flexible, so you can call them with different values.
You can do different things with the return values, as well. Try this sandbox.
Audit trail
An audit trail is a list of changes to data. If there's a question about the data, you can go back through the audit trail to reconstruct what happened. Most apps that handle financial data have audit trails.
Let's add an audit trail to the pig. You can try it. Each time something happens, we record it.
The audit trail is shown on the page for simplicity. In real apps, it would be written somewhere on a server.
Here's the new HTML:
<h3>Audit Trail</h3>
<p id="audit-trail"></p>
A header, and a wrapper for the audit trail.
Let's add a new variable to keep the audit trail in.
Each time we do something to the pig, we'll add a message to auditTrail
.
Each thing we do to the pig is a transaction. There are only two types of transaction in this app:
- Adding a coin to the pig.
- Emptying the pig.
Here is the new code for addCoin
:
BetterPiggyBank.addCoin = function(amount) {
...
BetterPiggyBank.logTransaction("Added " + amount + " to the pig.");
};
Here's the new code for emptying the pig:
BetterPiggyBank.takeFortune = function() {
...
BetterPiggyBank.logTransaction("Emptied the pig.");
};
logTransaction
takes one argument: the message. We want it to add the message to the audit trail.
Here's the code:
BetterPiggyBank.logTransaction = function(message) {
auditTrail += message + "<br>";
$("#audit-trail").html(auditTrail);
};
Recall that auditTrail
is a variable declared at the top of the code:
var pigFortune = 0;
var auditTrail = "";
So we append the message to the auditTrail
, and inject auditTrail
into the HTML.
One detail. Here's a screen shot again:
We want each message on a new line. HTML doesn't care about white space, so we need to add a tag to do the work. The <br>
tag does what we need. So…
auditTrail += message + "<br>";
auditTrail
accumulates HTML. This is a common pattern.
Summary
- Functions take arguments, also called parameters.
- Functions can return a value with the
return
statement. - It's common to accumulate HTML in a variable.