The story so far
The piggy bank app persisted data to localStorage
. localStorage
stores string data, with string keys.
Let's make something like the piggy bank app, but a bit more complex
Requirements
Goals
Make an app that tracks your coffee and chocolate expenses. When you buy some coffee, you enter the amount you spend, and it gets added to a coffee total. Same for chocolate.
The totals are persistent.
You can try a partial version of it. It's only the coffee part.
Screen
There's just one screen. Here's a mock up:
We should be careful with the reset button. Make sure that users don't erase data accidentally.
Events
- Add button: Take the user's new expenses, and add them to the current expenses.
- Reset button: After the user confirms that s/he wants to reset, set current expenses to zero.
- App start: Show current expenses.
Pseudocode
We'll need two variables to coordinate the code fragments. Let's call them coffeeExpense
, and chocolateExpense
.
Event: page load
Load coffeeExpense and chocolateExpense from localStorage
Show them
Event: Add button
The Add button processes new coffee and chocolate expenses. One or both of the input fields could be empty, though.
If there is a new coffee expense:
If the data is not valid:
Show an error message
Else:
Update coffeeExpense and localStorage
Show the new value
If there is a new chocolate expense:
If the data is not valid:
Show an error message
Else:
Update chocolateExpense and localStorage
Show the new value
Sure.
If there is a new coffee expense:
If the data is valid:
Update coffeeExpense and localStorage
Show the new value
Else:
Show an error message
If there is a new chocolate expense:
If the data is valid:
Update chocolateExpense and localStorage
Show the new value
Else:
Show an error message
Either way would be fine.
- Is the data numeric?
- Is it zero or more?
We could do other tests, but that will work for this app.
Event: Reset button
Here's the pseudocode:
If user confirms:
Set coffeeExpense and chocolateExpense to 0
Erase localStorage for them
Update the display
OK, let's implement.
HTML for coffee
Here's the display:
Let's work on coffee first. Get it working, then copy-and-paste for chocolate.
Line 4…
Coffee: $<span id="coffeeExpense"></span>
… creates an output place for coffee expense.
The input field is on lines 8 to 15:
<div class="form-group">
<label for="new-coffee-expense">Coffee</label>
<input type="number" class="form-control" id="new-coffee-expense"
aria-describedby="new-coffee-expense-help"
placeholder="New coffee expense">
<small id="new-coffee-expense-help" class="form-text text-muted">
Enter new expense, or leave empty.</small>
</div>
As before, this is a copy-and-paste from the Bootstrap docs, changed to make it what we want.
The buttons are as before. Here's one:
<button onclick="CoffeeChocolate.recordNewExpenses()"
type="button" class="btn btn-primary"
title="Add new expenses">Add</button>
Namespace and variable
<script>
"use strict";
var CoffeeChocolate = CoffeeChocolate || {};
(function($) {
var coffeeExpense = 0;
...
}(jQuery));
</script>
Note the variable coffeeExpense
. It ties together the code fragments attached to the events.
Events
We want code for three events:
- App start: Show current expenses.
- Add button: Take the user's new expenses, and add them to the current expenses.
- Reset button: After the user confirms that s/he wants to reset, set current expenses to zero.
Let's look at them.
Loading the page
The pseudocode from before:
Load coffeeExpense and chocolateExpense from localStorage
Show them
Remember that we're just doing coffee. We'll add chocolate later.
$(document).ready(function () {
//Template stuff
...
//Load initial expenses.
//Expense variables have already been initialized to zero.
if ( localStorage.getItem("coffeeExpense") ) {
coffeeExpense = parseFloat(localStorage.getItem("coffeeExpense"));
}
//Show current expenses.
CoffeeChocolate.showCurrentExpenses();
});
It's much the same as the piggy bank code, except for the last bit:
//Show current expenses.
CoffeeChocolate.showCurrentExpenses();
In the piggy bank app, we updated the amount shown on the screen three times:
- On page load
- When the add button was pressed
- When the "Take it all" button was pressed
It was just one line each time, but that line was repeated.
Let's move the screen updating code into its own function, that's called when needed. Here it is, so far:
/**
* Show current expenses.
*/
CoffeeChocolate.showCurrentExpenses = function() {
$("#coffeeExpense").html(coffeeExpense);
};
Why do this? Any ideas?
showCurrentExpenses
changes output for the entire app.
- App start: Show current expenses.
- Add button: Take the user's new expenses, and add them to the current expenses.
- Reset button: After the user confirms that s/he wants to reset, set current expenses to zero.
We've done the first one.
Add button
Here's the pseudocode for the second.
If there is a new coffee expense:
If the data is not valid:
Show an error message
Else:
Update coffeeExpense and localStorage
Show the new value
If there is a new chocolate expense:
If the data is not valid:
Show an error message
Else:
Update chocolateExpense and localStorage
Show the new value
Let's remove the chocolate stuff for now:
If there is a new coffee expense:
If the data is not valid:
Show an error message
Else:
Update coffeeExpense and localStorage
Show the new value
The code:
/**
* Record new expenses.
*/
CoffeeChocolate.recordNewExpenses = function() {
//Process new coffee expense.
var newCoffeeExpense = $("#new-coffee-expense").val();
//Anything found?
if ( newCoffeeExpense != "" ) {
//Something in the coffee field.
//Check that it is not negative.
if ( newCoffeeExpense < 0 ) {
alert("Sorry, coffee expense cannot be negative. (Wish it could be.)")
}
else {
//Coffee expense is OK.
coffeeExpense += parseFloat(newCoffeeExpense);
localStorage.setItem("coffeeExpense", coffeeExpense.toString());
}
}
//Show current expenses.
CoffeeChocolate.showCurrentExpenses();
//Clear the input fields.
$("#new-coffee-expense").val("");
};
There is one new thing here. Check this out:
Two variables are declared: coffeeExpense
, and newCoffeeExpense
. Each has its scope, that is, code where the variable exists.
coffeeExpense
is created inside the wrapper for $:
(function($) {
var coffeeExpense = 0;
...
}(jQuery));
It's available to the code inside that wrapper. Since all of the page's code is inside the wrapper (apart from the code in the HTML onclick="code here"
), the variable coffeeExpense
is available to all of the code.
newCoffeeExpense
is declared inside the function CoffeeChocolate.recordNewExpenses()
.
The variable is created when the browser starts running CoffeeChocolate.recordNewExpenses()
. It is destroyed when the function exits. newCoffeeExpense
is only available to code inside CoffeeChocolate.recordNewExpenses()
.
Variables like newCoffeeExpense
are called local variables. They only exist inside the functions that declare them.
The last line isn't in the pseudocode, but is needed:
$("#new-coffee-expense").val("");
It puts an empty string in the coffee expense field, erase what was there.
Some other reminders:
- Remember that
+=
means "add to," or "increase by." - The value of an
<input>
field is always a string, even when users type numbers.parseFloat()
converts a string to a number that can have a decimal part. localStorage
.localStorage
will only handle strings.toString()
handles that.
Good point. The check isn't needed in this case, because of the HTML we used:
<input type="number" ...
Because it's a number
field, the browser will only let users type in numbers.
Let's see where we are. Here are all of the events.
- App start: Show current expenses.
- Add button: Take the user's new expenses, and add them to the current expenses.
- Reset button: After the user confirms that s/he wants to reset, set current expenses to zero.
We've done the first two.
Reset button
Here's the pseudocode for the reset button, with the chocolate stuff omitted:
If user confirms:
Set coffeeExpense to 0
Erase localStorage for coffeeExpense
Update the display
Here's the JS:
Line 5…
if ( confirm("Reset expenses to zero?") ) {
…shows a dialog box with the given message, and an OK and Cancel button.
If the user clicks OK, the confirm()
function returns true. If the user clicks Cancel (or closes the dialog with the X in the upper right), the confirm()
function returns false.
Here's the code again:
The rest is stuff we've seen before. Remember that coffeeExpense
is a shared variable.
At this point, we should test the code, and make sure it works. And… it does!
Try it if you want.
Adding chocolate
Submit your app's URL.
(If you were logged in as a student, you could submit an exercise solution, and get some feedback.)
Summary
We made an app that uses input fields, validation, and localStorage
.
We're almost ready to start on CRUD apps. Only a little more before that.