Better pig (function arguments)

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:

Start

Here's what we had for the original app:

  1. <button onclick="PiggyBank.addCoin()"
  2.     type="button" class="btn btn-primary"
  3.     title="Put a penny in the pig"
  4. >Add a penny</button>
  5. ...
  6. PiggyBank.addCoin = function() {
  7.     pigFortune += 0.01;
  8.     localStorage.setItem("pigFortune", pigFortune.toString());
  9.     $("#amount").html(pigFortune);
  10. };
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.

  1. pigFortune += 0.01;
  2. localStorage.setItem("pigFortune", pigFortune.toString());
  3. $("#amount").html(pigFortune);
For a nickel, we only want to change one thing:

  1. pigFortune += 0.05;
  2. localStorage.setItem("pigFortune", pigFortune.toString());
  3. $("#amount").html(pigFortune);
What if we could put the coin's value in a variable?

  1. pigFortune += amount;
  2. localStorage.setItem("pigFortune", pigFortune.toString());
  3. $("#amount").html(pigFortune);
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:

Ack!

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?

  1. BetterPiggyBank.addCoin = function(amount) {
  2.     pigFortune += amount;
  3.     pigFortune = BetterPiggyBank.precisionRound(pigFortune, 2);
  4.     localStorage.setItem("pigFortune", pigFortune.toString());
  5.     $("#amount").html(pigFortune);
  6. };
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.

Arguments match by position

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.

Trail

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.

  1. <script>
  2.     "use strict";
  3.     var BetterPiggyBank = BetterPiggyBank || {};
  4.     (function($) {
  5.         var pigFortune = 0;
  6.         var auditTrail = "";
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:

Screen shot

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.

Pattern

Accumulate HTML

Situation:
Some HTML will get longer and longer as things happen on the page.
Actions:
When something relevant happens, append HTML to a variable. Inject the variable into the page.

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.