added Murach exercies
22
book_apps/ch01/die_roller/index.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Die Roller</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
<script>
|
||||||
|
// get a random number between 0 and 6
|
||||||
|
const randNum = Math.random() * 6;
|
||||||
|
|
||||||
|
// round up to get a number from 1 to 6.
|
||||||
|
const dieRoll = Math.ceil(randNum);
|
||||||
|
|
||||||
|
// write some text and the number to the web page
|
||||||
|
document.write("Die Roll: " + dieRoll);
|
||||||
|
</script>
|
||||||
|
</h1>
|
||||||
|
<p>Click Reload to roll again!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
book_apps/ch01/die_roller/main.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
12
book_apps/ch01/email_list/add.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Join Email List</title>
|
||||||
|
<link rel="stylesheet" href="email_list.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Thanks for joining our email list!</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
40
book_apps/ch01/email_list/email_list.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// define a function that gets an HTML element
|
||||||
|
function getElement(selector) {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// define a function that handles the click event of the Join button
|
||||||
|
function joinButtonClick(event) {
|
||||||
|
// get user entries from text boxes
|
||||||
|
const email1 = getElement("#email_1").value;
|
||||||
|
const email2 = getElement("#email_2").value;
|
||||||
|
|
||||||
|
// check user entries
|
||||||
|
let invalid = false;
|
||||||
|
if (email1 == "") {
|
||||||
|
getElement("#email_1_error").textContent = "Email is required.";
|
||||||
|
invalid = true;
|
||||||
|
} else {
|
||||||
|
getElement("#email_1_error").textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (email1 != email2) {
|
||||||
|
getElement("#email_2_error").textContent = "Emails must match.";
|
||||||
|
invalid = true;
|
||||||
|
} else {
|
||||||
|
getElement("#email_2_error").textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel form submit if any user entries are invalid
|
||||||
|
if (invalid) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// add code that's run when the web page is loaded
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// specify the function that's run when the Join button is clicked
|
||||||
|
getElement("#join_button").addEventListener("click", joinButtonClick);
|
||||||
|
});
|
||||||
32
book_apps/ch01/email_list/index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Email List</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Email List</h1>
|
||||||
|
<form id="email_form" name="email_form"
|
||||||
|
action="add.html" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="email_1">Enter email:</label>
|
||||||
|
<input type="text" id="email_1" name="email_1">
|
||||||
|
<span id="email_1_error">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="email_2">Confirm email:</label>
|
||||||
|
<input type="text" id="email_2" name="email_2">
|
||||||
|
<span id="email_2_error">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="submit" id="join_button" value="Join">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<script src="email_list.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
book_apps/ch01/email_list/main.css
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
13
book_apps/ch01/welcome/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Welcome</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome to Modern JavaScript</h1>
|
||||||
|
<script>
|
||||||
|
// call a method that displays a dialog
|
||||||
|
alert("Hi");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
book_apps/ch01/welcome/main.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
width: 600px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
9
book_apps/ch02/miles_to_kms/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Miles to Kilometers</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="miles_to_kms.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
book_apps/ch02/miles_to_kms/miles_to_kms.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// get miles from user
|
||||||
|
const miles = parseFloat(prompt("Miles:", 120));
|
||||||
|
|
||||||
|
// calculate kilometers
|
||||||
|
const kilometers = miles * 1.60934;
|
||||||
|
|
||||||
|
// display results
|
||||||
|
const results = "Miles: " + miles + "\n" +
|
||||||
|
"Kilometers: " + kilometers.toFixed(2);
|
||||||
|
|
||||||
|
alert(results);
|
||||||
9
book_apps/ch02/test_scores/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test Scores</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="test_scores.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
25
book_apps/ch02/test_scores/test_scores.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// initialize total variable
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
//get 3 scores from user and add them together
|
||||||
|
const score1 = parseInt(prompt("Enter first test score"));
|
||||||
|
total += score1;
|
||||||
|
|
||||||
|
const score2 = parseInt(prompt("Enter second test score"));
|
||||||
|
total += score2;
|
||||||
|
|
||||||
|
const score3 = parseInt(prompt("Enter third test score"));
|
||||||
|
total += score3;
|
||||||
|
|
||||||
|
//calculate the average
|
||||||
|
const average = Math.round(total/3);
|
||||||
|
|
||||||
|
// display the scores
|
||||||
|
const result = "Score 1 = " + score1 + "\n" +
|
||||||
|
"Score 2 = " + score2 + "\n" +
|
||||||
|
"Score 3 = " + score3 + "\n" +
|
||||||
|
"Average score = " + average;
|
||||||
|
|
||||||
|
alert(result);
|
||||||
32
book_apps/ch03/future_value/future_value.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// get investment amount - loop until user enters a number
|
||||||
|
let investment = NaN;
|
||||||
|
while (isNaN(investment)) {
|
||||||
|
investment = parseFloat(
|
||||||
|
prompt("Enter investment amount", 10000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get interest rate - loop until user enters a number
|
||||||
|
let rate = NaN;
|
||||||
|
while (isNaN(rate)) {
|
||||||
|
rate = parseFloat(prompt("Enter interest rate", 4.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get number of years - loop until user enters a number
|
||||||
|
let years = NaN;
|
||||||
|
while (isNaN(years)) {
|
||||||
|
years = parseInt(prompt("Enter years", 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// calulate future value
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 0; i < years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display results
|
||||||
|
alert("Investment amount: $" + investment + "\n" +
|
||||||
|
"Interest rate: " + rate + "%\n" +
|
||||||
|
"Years: " + years + "\n" +
|
||||||
|
"Future Value: $" + futureValue.toFixed(2));
|
||||||
11
book_apps/ch03/future_value/index.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
35
book_apps/ch03/guess_number/guess_number.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// get a random number between 1 and 20
|
||||||
|
const num = Math.ceil(Math.random() * 20);
|
||||||
|
|
||||||
|
// get the computer's guess
|
||||||
|
const computerGuess = Math.ceil(Math.random() * 20);
|
||||||
|
|
||||||
|
// get the user's guess
|
||||||
|
const userGuess = parseInt(prompt("Enter a number between 1 and 20"));
|
||||||
|
|
||||||
|
if (isNaN(userGuess)) {
|
||||||
|
alert("Not a valid number. Computer wins.")
|
||||||
|
} else if (userGuess < 1 || userGuess > 20) {
|
||||||
|
alert("Not a number between 1 and 20. Computer wins.");
|
||||||
|
} else {
|
||||||
|
let message = "The number is " + num + ".\n" +
|
||||||
|
"You guessed " + userGuess +
|
||||||
|
" and the computer guessed " + computerGuess + ".\n";
|
||||||
|
|
||||||
|
// compute the difference between the guesses and the number
|
||||||
|
const computerDiff = Math.abs(num - computerGuess);
|
||||||
|
const userDiff = Math.abs(num - userGuess);
|
||||||
|
|
||||||
|
// determine the winner
|
||||||
|
if (userDiff === computerDiff) {
|
||||||
|
message += "It's a tie!";
|
||||||
|
} else if (userDiff < computerDiff) {
|
||||||
|
message += "You WIN!";
|
||||||
|
} else {
|
||||||
|
message += "Computer wins.";
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(message);
|
||||||
|
}
|
||||||
13
book_apps/ch03/guess_number/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Guess the Number</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The Guess the Number App</h1>
|
||||||
|
<p>Click Reload to play again.</p>
|
||||||
|
<script src="guess_number.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
book_apps/ch03/magic_eight_ball/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Magic Eightball</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Magic Eightball</h1>
|
||||||
|
<p>Click Reload to ask another question.</p>
|
||||||
|
<script src="magic_eight_ball.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
book_apps/ch03/magic_eight_ball/magic_eight_ball.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// get user's question
|
||||||
|
const question = prompt("Please enter a yes or no question.")
|
||||||
|
|
||||||
|
if (question) {
|
||||||
|
let answer = "";
|
||||||
|
|
||||||
|
// get a random number between 1 and 9
|
||||||
|
const num = Math.ceil(Math.random() * 9);
|
||||||
|
|
||||||
|
switch (num) {
|
||||||
|
case 1:
|
||||||
|
answer = "It is certain.";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
answer = "Reply hazy, try again.";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
answer = "Don't count on it.";
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
answer = "It is decidedly so.";
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
answer = "Without a doubt.";
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
answer = "Ask again later.";
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
answer = "My sources say no.";
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
answer = "Most likely.";
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
answer = "Signs point to yes.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
answer = "Something went wrong.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
alert(question + "\n" +
|
||||||
|
answer);
|
||||||
|
} else {
|
||||||
|
alert("You didn't enter a question.");
|
||||||
|
}
|
||||||
29
book_apps/ch04/bio/bio.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// get name, dob, and colors and split into arrays
|
||||||
|
const names = prompt("Enter your full name").split(" ");
|
||||||
|
const dob = prompt("Enter your DOB in mm-dd-yyyy format").split("-");
|
||||||
|
const colors = prompt("Enter your favorite colors, separated by commas")
|
||||||
|
.split(",");
|
||||||
|
|
||||||
|
// capitalize each name
|
||||||
|
for (let i in names) {
|
||||||
|
const firstLetter = names[i].substring(0, 1).toUpperCase();
|
||||||
|
const restOfName = names[i].substring(1).toLowerCase();
|
||||||
|
names[i] = firstLetter + restOfName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim any spaces from colors
|
||||||
|
for (let i in colors) {
|
||||||
|
colors[i] = colors[i].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a display string for the colors
|
||||||
|
const firstColors = colors.slice(0, -1);
|
||||||
|
const lastColor = colors.at(-1);
|
||||||
|
const colorString = `${firstColors.join(", ")} and ${lastColor}`;
|
||||||
|
|
||||||
|
// display bio
|
||||||
|
alert(`Hello, my name is ${names.join(" ")}.
|
||||||
|
I was born in ${dob[2]}.
|
||||||
|
I have ${colors.length} favorite colors: ${colorString}.`);
|
||||||
10
book_apps/ch04/bio/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>The Bio App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="bio.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
book_apps/ch04/email_check/email_check.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
let isValid = false;
|
||||||
|
while (!isValid) {
|
||||||
|
const email = prompt("Enter your email address:");
|
||||||
|
if (email.startsWith("_") || email.startsWith(".")) {
|
||||||
|
alert("Email address may not start with a period or underscore.");
|
||||||
|
} else if (!email.includes("@")) {
|
||||||
|
alert("Email address must contain an @ symbol.");
|
||||||
|
} else if (!email.includes(".")) {
|
||||||
|
alert("Email address must contain a period.");
|
||||||
|
} else if (!email.includes(".", email.indexOf("@"))) {
|
||||||
|
alert("The period must come after the @ symbol.");
|
||||||
|
} else {
|
||||||
|
alert(`You entered: ${email}.\nThis is a valid email address.`);
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
book_apps/ch04/email_check/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>The Email Check App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="email_check.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
13
book_apps/ch04/test_scores/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Average Test Scores</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>The Test Scores App</h1>
|
||||||
|
<script src="test_scores.js"></script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
book_apps/ch04/test_scores/test_scores.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const scores = [];
|
||||||
|
|
||||||
|
// get scores from the user
|
||||||
|
while (true) {
|
||||||
|
const entry = prompt("Enter a test score. Or, enter 'x' to exit.");
|
||||||
|
if (entry === 'x' || entry === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const score = parseInt(entry);
|
||||||
|
if (score >= 0 && score <= 100) {
|
||||||
|
scores[scores.length] = score;
|
||||||
|
} else {
|
||||||
|
alert("Score must by a valid number from 0 through 100.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no scores, notify user
|
||||||
|
if (scores.length === 0) {
|
||||||
|
alert("You didn't enter any scores.");
|
||||||
|
} else { // otherwise, calculate the total and create a display string
|
||||||
|
let total = 0;
|
||||||
|
let scoresString = "";
|
||||||
|
for (let i in scores) {
|
||||||
|
total += scores[i];
|
||||||
|
scoresString += `Score ${parseInt(i) + 1}: ${scores[i]}\n`;
|
||||||
|
}
|
||||||
|
const average = total/scores.length;
|
||||||
|
|
||||||
|
// display the scores and their average
|
||||||
|
alert(`${scoresString}Average score: ${average.toFixed(2)}`);
|
||||||
|
}
|
||||||
13
book_apps/ch04/test_scores2/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Average Test Scores</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>The Test Scores App</h1>
|
||||||
|
<script src="test_scores.js"></script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
book_apps/ch04/test_scores2/test_scores.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const scores = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const entry = prompt("Enter a test score. Or, enter 'x' to exit.");
|
||||||
|
if (entry === 'x' || entry === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const score = parseInt(entry);
|
||||||
|
if (score >= 0 && score <= 100) {
|
||||||
|
scores.push(score);
|
||||||
|
} else {
|
||||||
|
alert("Score must by a valid number from 0 through 100.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = scores.length;
|
||||||
|
if (len === 0) {
|
||||||
|
alert("You didn't enter any scores.");
|
||||||
|
} else {
|
||||||
|
// calculate total and average
|
||||||
|
let total = 0;
|
||||||
|
for (let score of scores) {
|
||||||
|
total += score;
|
||||||
|
}
|
||||||
|
const average = total/len;
|
||||||
|
|
||||||
|
// get the last 3 scores in reverse order
|
||||||
|
const lastScores = (len <= 3) ? scores.slice() : scores.slice(len - 3, len);
|
||||||
|
lastScores.reverse();
|
||||||
|
|
||||||
|
// display score data
|
||||||
|
alert("Scores: " + scores.join(", ") + "\n" +
|
||||||
|
"Total: " + total + "\n" +
|
||||||
|
"Average: " + average.toFixed(2) + "\n" +
|
||||||
|
"Last 3: " + lastScores.join(", "));
|
||||||
|
}
|
||||||
46
book_apps/ch05/bio/bio.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getAsArray(promptMsg, separator = " ") {
|
||||||
|
return prompt(promptMsg).split(separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use rest operator for the following functions so can accept either a
|
||||||
|
// comma-separated list of arguments or an array with a spread operator
|
||||||
|
function capitalize(...words) {
|
||||||
|
const capitalizedWords = [];
|
||||||
|
for (let word of words) {
|
||||||
|
const firstLetter = word.substring(0,1).toUpperCase();
|
||||||
|
const restOfWord = word.substring(1).toLowerCase();
|
||||||
|
capitalizedWords.push(firstLetter + restOfWord);
|
||||||
|
}
|
||||||
|
return capitalizedWords;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim(...items) {
|
||||||
|
const trimmedItems = [];
|
||||||
|
for (let item of items) {
|
||||||
|
trimmedItems.push(item.trim());
|
||||||
|
}
|
||||||
|
return trimmedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorsString(...items) {
|
||||||
|
const firstItems = items.slice(0, -1);
|
||||||
|
const lastItem = items.at(-1);
|
||||||
|
return `${firstItems.join(", ")} and ${lastItem}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayBio(names, dob, colors) {
|
||||||
|
alert("Hello, my name is " + names.join(" ") + ".\n" +
|
||||||
|
"I was born in " + dob.at(-1) + ".\n" +
|
||||||
|
"I have " + colors.length + " favorite colors: " +
|
||||||
|
getColorsString(...colors) + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
const names = capitalize(...getAsArray("Enter your full name"));
|
||||||
|
const dob = getAsArray("Enter your DOB in mm-dd-yyyy format", "-");
|
||||||
|
|
||||||
|
const msg = "Enter your favorite colors, separated by commas";
|
||||||
|
const colors = trim(...getAsArray(msg, ","));
|
||||||
|
|
||||||
|
displayBio(names, dob, colors);
|
||||||
9
book_apps/ch05/bio/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>The Bio App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="bio.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
book_apps/ch05/future_value/future_value.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getNumber(promptMsg, defaultValue) {
|
||||||
|
let num = NaN;
|
||||||
|
while (isNaN(num)) {
|
||||||
|
num = parseFloat(prompt(promptMsg, defaultValue));
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcFutureValue(investment, rate, years) {
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 0; i < years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
}
|
||||||
|
return futureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayResults(investment, rate, years, futureValue) {
|
||||||
|
alert("Investment amount: $" + investment + "\n" +
|
||||||
|
"Interest rate: " + rate + "%\n" +
|
||||||
|
"Years: " + years + "\n" +
|
||||||
|
"Future Value: $" + futureValue.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
const investment = getNumber("Enter investment amount as xxxxx.xx", 10000);
|
||||||
|
const rate = getNumber("Enter interest rate as xx.x", 7.5);
|
||||||
|
const years = getNumber("Enter number of years", 10);
|
||||||
|
const futureValue = calcFutureValue(investment, rate, years);
|
||||||
|
displayResults(investment, rate, years, futureValue);
|
||||||
12
book_apps/ch05/future_value/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The Future Value Calculator</h1>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
51
book_apps/ch05/guess_number/guess.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// global variables
|
||||||
|
let randomNum = 0;
|
||||||
|
let tries = 0;
|
||||||
|
|
||||||
|
// helper function
|
||||||
|
const getRandomInt = (max = 100) => {
|
||||||
|
let num = Math.random() * max; // get a random number between 0 and max
|
||||||
|
num = Math.ceil(num); // round up to nearest integer
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
|
||||||
|
// event handler functions
|
||||||
|
const guessClick = () => {
|
||||||
|
const guess = parseInt(document.querySelector("#number").value);
|
||||||
|
|
||||||
|
let message = "";
|
||||||
|
if (isNaN(guess)) {
|
||||||
|
message = "Not a valid number. Please enter a valid number."
|
||||||
|
} else if (guess < 1 || guess > 10) {
|
||||||
|
message = "Invalid number. Enter a number between 1 and 10.";
|
||||||
|
} else if (guess < randomNum) {
|
||||||
|
message = "Too small. Try again.";
|
||||||
|
tries++;
|
||||||
|
} else if (guess > randomNum) {
|
||||||
|
message = "Too big. Try again.";
|
||||||
|
tries++;
|
||||||
|
} else if (guess === randomNum) {
|
||||||
|
tries++;
|
||||||
|
const lastWord = (tries === 1) ? "try" : "tries";
|
||||||
|
message = `You guessed it in ${tries} ${lastWord}!`;
|
||||||
|
}
|
||||||
|
document.querySelector("#message").textContent = message;
|
||||||
|
};
|
||||||
|
|
||||||
|
const playAgainClick = () => {
|
||||||
|
randomNum = getRandomInt(10);
|
||||||
|
tries = 0;
|
||||||
|
document.querySelector("#number").value = "";
|
||||||
|
document.querySelector("#message").textContent = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
playAgainClick(); // initial a new game
|
||||||
|
|
||||||
|
document.querySelector("#guess").addEventListener(
|
||||||
|
"click", guessClick);
|
||||||
|
document.querySelector("#play_again").addEventListener(
|
||||||
|
"click", playAgainClick);
|
||||||
|
});
|
||||||
20
book_apps/ch05/guess_number/index.html
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Guess the Number</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Guess the Number</h1>
|
||||||
|
<p>It's between 1 and 10.</p>
|
||||||
|
|
||||||
|
<input type="text" id="number">
|
||||||
|
<button id="guess">Guess</button>
|
||||||
|
<button id="play_again">Play again</button><br>
|
||||||
|
<label id="message"></label>
|
||||||
|
|
||||||
|
<script src="guess.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
15
book_apps/ch05/guess_number/main.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
input, button {
|
||||||
|
margin: 0 0.5em 1em 0;
|
||||||
|
}
|
||||||
14
book_apps/ch05/typewriter/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My Typewriter App</title>
|
||||||
|
<link rel="stylesheet" href="typewriter.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My Typewriter App</h1>
|
||||||
|
<pre id="text"></pre>
|
||||||
|
<script src="typewriter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
book_apps/ch05/typewriter/typewriter.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
23
book_apps/ch05/typewriter/typewriter.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
|
document.addEventListener("keydown", event => {
|
||||||
|
const pre = document.querySelector("#text");
|
||||||
|
|
||||||
|
const validChars = `abcdefghijklmnopqrstuvwxyz
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
1234567890~!@#$%^&*()_+-=\;:'",.`;
|
||||||
|
|
||||||
|
if (validChars.includes(event.key)) {
|
||||||
|
pre.textContent += event.key;
|
||||||
|
}
|
||||||
|
else if (event.key.toLowerCase() === "enter") {
|
||||||
|
pre.textContent += "\n";
|
||||||
|
}
|
||||||
|
else if (event.key.toLowerCase() === "backspace") {
|
||||||
|
// remove last character
|
||||||
|
pre.textContent = pre.textContent.slice(0, -1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
41
book_apps/ch06/faqs/faqs.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 500px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: 120%;
|
||||||
|
padding: 0 0 0 1.5em;
|
||||||
|
cursor: pointer;
|
||||||
|
background: url(images/plus.png) no-repeat left center;
|
||||||
|
}
|
||||||
|
h2.minus {
|
||||||
|
background: url(images/minus.png) no-repeat left center;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:focus, a:hover {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
padding-bottom: .25em;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
padding-bottom: .25em;
|
||||||
|
padding-left: 2em;
|
||||||
|
}
|
||||||
24
book_apps/ch06/faqs/faqs.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
// the event handler for the click event of each <h2> element
|
||||||
|
const toggleVisibility = evt => {
|
||||||
|
const h2 = evt.currentTarget; // get the <h2> element
|
||||||
|
const div = h2.nextElementSibling; // get the <div> element
|
||||||
|
|
||||||
|
h2.classList.toggle("minus");
|
||||||
|
div.classList.toggle("open");
|
||||||
|
|
||||||
|
evt.preventDefault(); // cancel default action of child <a> element
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// get the <h2> elements
|
||||||
|
const h2s = document.querySelectorAll("#faqs h2");
|
||||||
|
|
||||||
|
// attach event handler for each <h2> tag
|
||||||
|
for (let h2 of h2s) {
|
||||||
|
h2.addEventListener("click", toggleVisibility);
|
||||||
|
}
|
||||||
|
// set focus on first <a> tag
|
||||||
|
h2s[0].firstChild.focus();
|
||||||
|
});
|
||||||
BIN
book_apps/ch06/faqs/images/minus.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
book_apps/ch06/faqs/images/plus.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
35
book_apps/ch06/faqs/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>FAQs</title>
|
||||||
|
<link rel="stylesheet" href="faqs.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="faqs">
|
||||||
|
<h1>JavaScript FAQs</h1>
|
||||||
|
|
||||||
|
<h2><a href="#">What is JavaScript?</a></h2>
|
||||||
|
<div>
|
||||||
|
<p>JavaScript is a scripting language that you can use to make websites
|
||||||
|
interactive.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2><a href="#">Why use JavaScript?</a></h2>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>It has simple syntax that's easy to learn.</li>
|
||||||
|
<li>It's versatile.</li>
|
||||||
|
<li>It's one of the most popular programming languages.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2><a href="#">Which browsers support JavaScript?</a></h2>
|
||||||
|
<div>
|
||||||
|
<p>All the major modern browsers support JavaScript.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="faqs.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
book_apps/ch06/image_swap/image_swap.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 550px;
|
||||||
|
padding: 0 1em 1em 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
32
book_apps/ch06/image_swap/image_swap.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// get the main image and caption elements
|
||||||
|
const mainImage = getElement("#main_image");
|
||||||
|
const caption = getElement("#caption");
|
||||||
|
|
||||||
|
// get all the <a> elements in the <ul> element
|
||||||
|
const imageLinks = document.querySelectorAll("#image_list a");
|
||||||
|
|
||||||
|
// process image links
|
||||||
|
for ( let link of imageLinks ) {
|
||||||
|
|
||||||
|
// preload image
|
||||||
|
const image = new Image();
|
||||||
|
image.src = link.href;
|
||||||
|
|
||||||
|
// attach event handler for click event of <a> element
|
||||||
|
link.addEventListener("click", evt => {
|
||||||
|
mainImage.src = link.href;
|
||||||
|
mainImage.alt = link.title;
|
||||||
|
caption.textContent = link.title;
|
||||||
|
|
||||||
|
evt.preventDefault(); // cancel the default action of the link
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// set focus on first image link
|
||||||
|
imageLinks[0].focus();
|
||||||
|
});
|
||||||
BIN
book_apps/ch06/image_swap/images/bison.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
book_apps/ch06/image_swap/images/deer.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
book_apps/ch06/image_swap/images/hero.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
book_apps/ch06/image_swap/images/release.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
30
book_apps/ch06/image_swap/index.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Fishing Images Viewer</title>
|
||||||
|
<link rel="stylesheet" href="image_swap.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Fishing Images</h1>
|
||||||
|
<p>Click on an image to enlarge.</p>
|
||||||
|
|
||||||
|
<ul id="image_list">
|
||||||
|
<li><a href="images/release.jpg" title="Catch and Release">
|
||||||
|
<img src="thumbnails/release.jpg" alt="release fish"></a></li>
|
||||||
|
<li><a href="images/deer.jpg" title="Deer at Play">
|
||||||
|
<img src="thumbnails/deer.jpg" alt="deer"></a></li>
|
||||||
|
<li><a href="images/hero.jpg" title="The Big One!">
|
||||||
|
<img src="thumbnails/hero.jpg" alt="big fish"></a></li>
|
||||||
|
<li><a href="images/bison.jpg" title="Roaming Bison">
|
||||||
|
<img src="thumbnails/bison.jpg" alt="Bison"></a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2 id="caption">Catch and Release</h2>
|
||||||
|
<p><img id="main_image" src="images/release.jpg"
|
||||||
|
alt="Catch and Release"></p>
|
||||||
|
|
||||||
|
<script src="image_swap.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
book_apps/ch06/image_swap/thumbnails/bison.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
book_apps/ch06/image_swap/thumbnails/deer.jpg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
book_apps/ch06/image_swap/thumbnails/hero.jpg
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
book_apps/ch06/image_swap/thumbnails/release.jpg
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
17
book_apps/ch06/register/confirm.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Thanks for registering with us!</h1>
|
||||||
|
<p>You should get a confirmation email shortly.</p>
|
||||||
|
<p><a href="index.html">Go back</a></p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
book_apps/ch06/register/index.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Register for an Account</h1>
|
||||||
|
<form action="confirm.html" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="email_address">E-Mail:</label>
|
||||||
|
<input type="text" name="email_address" id="email_address">
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="phone">Mobile Phone:</label>
|
||||||
|
<input type="text" name="phone" id="phone">
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="country">Country:</label>
|
||||||
|
<select name="country" id="country">
|
||||||
|
<option value="">Select a country</option>
|
||||||
|
<option>USA</option>
|
||||||
|
<option>Canada</option>
|
||||||
|
<option>Mexico</option>
|
||||||
|
</select>
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Contact me by:</label>
|
||||||
|
<input type="radio" name="contact" id="text"
|
||||||
|
value="text" checked>Text
|
||||||
|
<input type="radio" name="contact" id="email"
|
||||||
|
value="email">Email
|
||||||
|
<input type="radio" name="contact" id="none"
|
||||||
|
value="none">Don't contact me
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Terms of Service:</label>
|
||||||
|
<input type="checkbox" name="terms" id="terms"
|
||||||
|
value="yes">I accept
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="button" id="register" value="Register">
|
||||||
|
<input type="button" id="reset_form" value="Reset">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<script src="register.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
book_apps/ch06/register/register.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: slategray;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input, select {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 13em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
61
book_apps/ch06/register/register.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const processEntries = () => {
|
||||||
|
// get form controls to check for validity
|
||||||
|
const email = getElement("#email_address");
|
||||||
|
const phone = getElement("#phone");
|
||||||
|
const country = getElement("#country");
|
||||||
|
const terms = getElement("#terms");
|
||||||
|
|
||||||
|
// check user entries for validity
|
||||||
|
let isValid = true;
|
||||||
|
if (email.value == "") {
|
||||||
|
email.nextElementSibling.textContent = "This field is required.";
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
email.nextElementSibling.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (phone.value == "") {
|
||||||
|
phone.nextElementSibling.textContent = "This field is required.";
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
phone.nextElementSibling.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (country.value == "") {
|
||||||
|
country.nextElementSibling.textContent = "Please select a country.";
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
country.nextElementSibling.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!terms.checked) {
|
||||||
|
terms.nextElementSibling.textContent = "This box must be checked.";
|
||||||
|
isValid = false;
|
||||||
|
} else {
|
||||||
|
terms.nextElementSibling.textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// submit the form if all fields are valid
|
||||||
|
if (isValid) {
|
||||||
|
getElement("form").submit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
getElement("form").reset();
|
||||||
|
getElement("#email_address").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#phone").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#country").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#terms").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#email_address").focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
getElement("#register").addEventListener("click", processEntries);
|
||||||
|
getElement("#reset_form").addEventListener("click", resetForm);
|
||||||
|
getElement("#email_address").focus();
|
||||||
|
});
|
||||||
17
book_apps/ch06/register_2.0/confirm.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Thanks for registering with us!</h1>
|
||||||
|
<p>You should get a confirmation email shortly.</p>
|
||||||
|
<p><a href="index.html">Go back</a></p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
book_apps/ch06/register_2.0/index.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Register for an Account</h1>
|
||||||
|
<form action="confirm.html" method="post">
|
||||||
|
<div>
|
||||||
|
<label for="email_address">E-Mail:</label>
|
||||||
|
<input type="text" name="email_address" id="email_address">
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="phone">Mobile Phone:</label>
|
||||||
|
<input type="text" name="phone" id="phone">
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="country">Country:</label>
|
||||||
|
<select name="country" id="country">
|
||||||
|
<option value="">Select a country</option>
|
||||||
|
<option>USA</option>
|
||||||
|
<option>Canada</option>
|
||||||
|
<option>Mexico</option>
|
||||||
|
</select>
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Contact me by:</label>
|
||||||
|
<input type="radio" name="contact" id="text"
|
||||||
|
value="text" checked>Text
|
||||||
|
<input type="radio" name="contact" id="email"
|
||||||
|
value="email">Email
|
||||||
|
<input type="radio" name="contact" id="none"
|
||||||
|
value="none">Don't contact me
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Terms of Service:</label>
|
||||||
|
<input type="checkbox" name="terms" id="terms"
|
||||||
|
value="yes">I accept
|
||||||
|
<span>*</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="button" id="register" value="Register">
|
||||||
|
<input type="button" id="reset_form" value="Reset">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<script src="register.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
39
book_apps/ch06/register_2.0/register.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: slategray;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input, select {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 13em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.messages {
|
||||||
|
color: red;
|
||||||
|
padding: 1em 2em;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border: 2px solid red;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
79
book_apps/ch06/register_2.0/register.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const displayErrorMsgs = msgs => {
|
||||||
|
// create a new ul element
|
||||||
|
const ul = document.createElement("ul");
|
||||||
|
ul.classList.add("messages");
|
||||||
|
|
||||||
|
// create a new li element for each error message, add to ul
|
||||||
|
for (let msg of msgs) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
const text = document.createTextNode(msg);
|
||||||
|
li.appendChild(text);
|
||||||
|
ul.appendChild(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if ul node isn't in document yet, add it
|
||||||
|
const node = getElement("ul");
|
||||||
|
if (node == null) {
|
||||||
|
// get the form element
|
||||||
|
const form = getElement("form");
|
||||||
|
|
||||||
|
// add ul to parent of form, before the form
|
||||||
|
form.parentNode.insertBefore(ul, form);
|
||||||
|
} else {
|
||||||
|
// replace existing ul node
|
||||||
|
node.parentNode.replaceChild(ul, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processEntries = () => {
|
||||||
|
// get form controls to check for validity
|
||||||
|
const email = getElement("#email_address");
|
||||||
|
const phone = getElement("#phone");
|
||||||
|
const country = getElement("#country");
|
||||||
|
const terms = getElement("#terms");
|
||||||
|
|
||||||
|
// create array for error messages
|
||||||
|
const msgs = [];
|
||||||
|
|
||||||
|
// check user entries for validity
|
||||||
|
if (email.value === "") {
|
||||||
|
msgs.push("Please enter an email address.");
|
||||||
|
}
|
||||||
|
if (phone.value === "") {
|
||||||
|
msgs.push("Please enter a mobile phone number.");
|
||||||
|
}
|
||||||
|
if (country.value === "") {
|
||||||
|
msgs.push("Please select a country.");
|
||||||
|
}
|
||||||
|
if (!terms.checked) {
|
||||||
|
msgs.push("You must agree to the terms of service.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// submit the form or notify user of errors
|
||||||
|
if (msgs.length === 0) { // no error messages
|
||||||
|
getElement("form").submit();
|
||||||
|
} else {
|
||||||
|
displayErrorMsgs(msgs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
getElement("form").reset();
|
||||||
|
|
||||||
|
// remove error messages if any
|
||||||
|
const ul = getElement("ul");
|
||||||
|
if (ul !== null) ul.remove();
|
||||||
|
|
||||||
|
getElement("#email_address").focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
getElement("#register").addEventListener("click", processEntries);
|
||||||
|
getElement("#reset_form").addEventListener("click", resetForm);
|
||||||
|
|
||||||
|
getElement("#email_address").focus();
|
||||||
|
});
|
||||||
24
book_apps/ch07/future_value/future_value.css
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 15em;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
68
book_apps/ch07/future_value/future_value.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getElement(selector) {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotValid(val) {
|
||||||
|
/*
|
||||||
|
if (isNaN(val)) {
|
||||||
|
console.error("Value is not a number");
|
||||||
|
} else if (val <= 0) {
|
||||||
|
console.warn(`Value ${val} is not greater than zero.`);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return isNaN(val) || val <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcFutureValue(investment, rate, years) {
|
||||||
|
// console.log("calcFutureValue() has started");
|
||||||
|
// console.trace();
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 1; i <= years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
// console.log(`year ${i} future value is ${futureValue}`);
|
||||||
|
}
|
||||||
|
return futureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcButtonClick() {
|
||||||
|
// clear any previous calculation
|
||||||
|
getElement("#future_value").value = "";
|
||||||
|
|
||||||
|
// get values user entered in text boxes
|
||||||
|
const investment = parseFloat(getElement("#investment").value);
|
||||||
|
const rate = parseFloat(getElement("#rate").value);
|
||||||
|
const years = parseInt(getElement("#years").value);
|
||||||
|
|
||||||
|
// check user entries and set error message
|
||||||
|
let errorMsg = "";
|
||||||
|
if (isNotValid(investment)) {
|
||||||
|
errorMsg += "Investment amount must be a positive number.\n";
|
||||||
|
getElement("#investment").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(rate)) {
|
||||||
|
errorMsg += "Interest rate must be a positive number.\n";
|
||||||
|
getElement("#rate").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(years)) {
|
||||||
|
errorMsg += "Number of years must be a positive number.";
|
||||||
|
getElement("#years").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user entries are valid, calculate and display future value
|
||||||
|
if (errorMsg == "") {
|
||||||
|
const futureValue = calcFutureValue(investment, rate, years);
|
||||||
|
getElement("#future_value").value = futureValue.toFixed(2);
|
||||||
|
} else {
|
||||||
|
alert(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// attach the button click event handler
|
||||||
|
getElement("#calculate").addEventListener("click", calcButtonClick);
|
||||||
|
|
||||||
|
// set focus on first text box on initial load
|
||||||
|
getElement("#investment").focus();
|
||||||
|
});
|
||||||
33
book_apps/ch07/future_value/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
<link rel="stylesheet" href="future_value.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The Future Value Calculator</h1>
|
||||||
|
<div>
|
||||||
|
<label for="investment">Total investment:</label>
|
||||||
|
<input type="text" id="investment" value="1000">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="rate">Annual interest rate:</label>
|
||||||
|
<input type="text" id="rate" value="3.5">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="years">Number of years</label>
|
||||||
|
<input type="text" id="years" value="10">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Future value:</label>
|
||||||
|
<input type="text" id="future_value" disabled>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="button" id="calculate" value="Calculate">
|
||||||
|
</div>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getElement(selector) {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotValid(val) {
|
||||||
|
return isNaN(val) || val <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcFutureValue(investment, rate, years) {
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 0; i < years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
}
|
||||||
|
return futureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcButtonClick() {
|
||||||
|
// clear any previous calculation
|
||||||
|
getElement("#future_value").value = "";
|
||||||
|
|
||||||
|
// get values user entered in textboxes
|
||||||
|
const investment = parseFloat(getElement("#investment").value);
|
||||||
|
const rate = parseFloat(getElement("#rate").value);
|
||||||
|
const years = parseInt(getElement("#years").value);
|
||||||
|
|
||||||
|
// check user entries and set error message
|
||||||
|
let errorMsg = "";
|
||||||
|
if (isNotValid(investment)) {
|
||||||
|
errorMsg += "Investment amount must be a positive number.\n";
|
||||||
|
getElement("#investment").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(rate)) {
|
||||||
|
errorMsg += "Interest rate must be a positive number.\n";
|
||||||
|
getElement("#rate").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(years)) {
|
||||||
|
errorMsg += "Number of years must be a positive number.";
|
||||||
|
getElement("#years").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user entries are valid, calculate and display future value
|
||||||
|
if (errorMsg == "") {
|
||||||
|
const futureValue = calcFutureValue(investment, rate, years);
|
||||||
|
getElement("#future_value").value = futureValue.toFixed(2);
|
||||||
|
} else {
|
||||||
|
alert(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// attach the event handler
|
||||||
|
getElement("#calculate").addEventListener("click", calcButtonClick);
|
||||||
|
|
||||||
|
// set focus on first text box on initial load
|
||||||
|
getElement("#investment").focus();
|
||||||
|
});
|
||||||
33
book_apps/ch07/future_value_no_debug_statements/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
<link rel="stylesheet" href="future_value.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The Future Value Calculator</h1>
|
||||||
|
<div>
|
||||||
|
<label for="investment">Total investment:</label>
|
||||||
|
<input type="text" id="investment" value="1000">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="Rate">Annual interest rate:</label>
|
||||||
|
<input type="text" id="rate" value="3.5">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="years">Number of years</label>
|
||||||
|
<input type="text" id="years" value="10">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Future value:</label>
|
||||||
|
<input type="text" id="future_value" value="" disabled>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="button" id="calculate" value="Calculate">
|
||||||
|
</div>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
book_apps/ch08/clock/clock.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const padValue = value => value.toString().padStart(2, "0");
|
||||||
|
|
||||||
|
const displayClock = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const ampm = (now.getHours() >= 12) ? "PM" : "AM";
|
||||||
|
const hours = (now.getHours() > 12) ? now.getHours() - 12 : now.getHours();
|
||||||
|
const minutes = padValue(now.getMinutes());
|
||||||
|
const seconds = padValue(now.getSeconds());
|
||||||
|
|
||||||
|
getElement("#time").textContent = `${hours}:${minutes}:${seconds} ${ampm}`;
|
||||||
|
getElement("#date").textContent = now.toDateString();
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// set initial clock time and then start interval for clock
|
||||||
|
displayClock();
|
||||||
|
setInterval(displayClock, 1000);
|
||||||
|
});
|
||||||
15
book_apps/ch08/clock/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Clock</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My Clock</h1>
|
||||||
|
<p id="time"></p>
|
||||||
|
<p id="date"></p>
|
||||||
|
<script src="clock.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
book_apps/ch08/clock/main.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 150px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: large;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
36
book_apps/ch08/countdown/countdown.css
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 450px;
|
||||||
|
padding: 0 1em 0 2em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
height: 1.5em;
|
||||||
|
width: 20em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
height: 2em;
|
||||||
|
width: 10em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
}
|
||||||
|
#message {
|
||||||
|
color: red;
|
||||||
|
font-weight: bold;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
47
book_apps/ch08/countdown/countdown.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
|
getElement("#countdown").addEventListener("click", () => {
|
||||||
|
const eventName = getElement("#event").value;
|
||||||
|
const eventDateString = getElement("#date").value;
|
||||||
|
const messageLbl = getElement("#message");
|
||||||
|
|
||||||
|
// make sure user entered event and date
|
||||||
|
if (eventName == "" || eventDateString == "") {
|
||||||
|
messageLbl.textContent = "Please enter both a name and a date.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert event date string to Date object and check for validity
|
||||||
|
const eventDate = new Date(eventDateString);
|
||||||
|
if (eventDate.toString() == "Invalid Date") {
|
||||||
|
messageLbl.textContent = "Please enter a valid date.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate days
|
||||||
|
const today = new Date();
|
||||||
|
const msFromToday = eventDate.getTime() - today.getTime();
|
||||||
|
const msForOneDay = 24 * 60 * 60 * 1000; // hrs * mins * secs * milliseconds
|
||||||
|
const daysToDate = Math.ceil( msFromToday / msForOneDay );
|
||||||
|
|
||||||
|
// create and display message
|
||||||
|
const displayDate = eventDate.toDateString();
|
||||||
|
let msg = "";
|
||||||
|
if (daysToDate == 0) {
|
||||||
|
msg = `Hooray! Today is ${eventName}! (${displayDate})`;
|
||||||
|
} else if (daysToDate > 0) {
|
||||||
|
msg = `${daysToDate} day(s) until ${eventName}! (${displayDate})`;
|
||||||
|
} else if (daysToDate < 0) {
|
||||||
|
msg = `${eventName} happened ${Math.abs(daysToDate)}
|
||||||
|
day(s) ago. (${displayDate})`;
|
||||||
|
}
|
||||||
|
messageLbl.textContent = msg;
|
||||||
|
});
|
||||||
|
|
||||||
|
// set focus on first text box
|
||||||
|
getElement("#event").focus();
|
||||||
|
});
|
||||||
28
book_apps/ch08/countdown/index.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Countdown</title>
|
||||||
|
<link rel="stylesheet" href="countdown.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Countdown To...</h1>
|
||||||
|
<div>
|
||||||
|
<label for="event">Event Name:</label>
|
||||||
|
<input type="text" id="event">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="date">Event Date:</label>
|
||||||
|
<input type="text" id="date"><br>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<button id="countdown">Countdown!</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label id="message"></label>
|
||||||
|
</div>
|
||||||
|
<script src="countdown.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
book_apps/ch08/slide_show/images/bison.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
book_apps/ch08/slide_show/images/deer.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
book_apps/ch08/slide_show/images/hero.jpg
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
book_apps/ch08/slide_show/images/release.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
21
book_apps/ch08/slide_show/index.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Fishing Slide Show</title>
|
||||||
|
<link rel="stylesheet" href="slide_show.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Fishing Slide Show</h1>
|
||||||
|
<ul id="image_list">
|
||||||
|
<li><a href="images/release.jpg" title="Catch and Release"></a></li>
|
||||||
|
<li><a href="images/deer.jpg" title="Deer at Play"></a></li>
|
||||||
|
<li><a href="images/hero.jpg" title="The Big One!"></a></li>
|
||||||
|
<li><a href="images/bison.jpg" title="Roaming Bison"></a></li>
|
||||||
|
</ul>
|
||||||
|
<p><img id="main_image"></p>
|
||||||
|
<h2 id="caption"></h2>
|
||||||
|
<script src="slide_show.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
16
book_apps/ch08/slide_show/slide_show.css
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 500px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h1, h2 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
46
book_apps/ch08/slide_show/slide_show.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// get elements for the image and caption
|
||||||
|
const mainImage = getElement("#main_image"); // the img element for the show
|
||||||
|
const caption = getElement("#caption"); // the h2 element for the caption
|
||||||
|
|
||||||
|
// get all the <a> elements in the <ul> element
|
||||||
|
const links = document.querySelectorAll("#image_list a");
|
||||||
|
|
||||||
|
// Process images
|
||||||
|
const imageCache = [];
|
||||||
|
let image = null;
|
||||||
|
|
||||||
|
for (let link of links) {
|
||||||
|
// Preload image
|
||||||
|
image = new Image();
|
||||||
|
image.src = link.href;
|
||||||
|
image.alt = link.title;
|
||||||
|
// add image to array
|
||||||
|
imageCache.push(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set initial image and caption
|
||||||
|
mainImage.src = imageCache[0].src;
|
||||||
|
mainImage.alt = imageCache[0].alt;
|
||||||
|
caption.textContent = imageCache[0].alt;
|
||||||
|
|
||||||
|
// start slide show
|
||||||
|
let imageCounter = 0;
|
||||||
|
setInterval(() => { // first parameter – anonymous function
|
||||||
|
// calculate the index for the current image
|
||||||
|
imageCounter = (imageCounter + 1) % imageCache.length;
|
||||||
|
|
||||||
|
// get image object from array
|
||||||
|
image = imageCache[imageCounter];
|
||||||
|
|
||||||
|
// set image and caption with values from image object
|
||||||
|
mainImage.src = image.src;
|
||||||
|
mainImage.alt = image.alt;
|
||||||
|
caption.textContent = image.alt;
|
||||||
|
},
|
||||||
|
2000); // second parameter - 2 second interval
|
||||||
|
});
|
||||||
15
book_apps/ch08/timer/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Timer</title>
|
||||||
|
<link rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My Timer</h1>
|
||||||
|
<p id="display_timer"></p>
|
||||||
|
<button id="start_timer">Start Timer</button>
|
||||||
|
<script src="timer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
book_apps/ch08/timer/main.css
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 150px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: large;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
height: 3em;
|
||||||
|
min-width: 10em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
41
book_apps/ch08/timer/timer.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
let timer = null;
|
||||||
|
let endTime = null;
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const padValue = value => value.toString().padStart(2, "0");
|
||||||
|
|
||||||
|
const displayTimer = () => {
|
||||||
|
const now = new Date();
|
||||||
|
const diff = endTime.getTime() - now.getTime();
|
||||||
|
if (diff <= 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
getElement("#display_timer").textContent = "";
|
||||||
|
alert("Time's up!!");
|
||||||
|
} else {
|
||||||
|
const timerDate = new Date(diff);
|
||||||
|
const minutes = padValue(timerDate.getMinutes());
|
||||||
|
const seconds = padValue(timerDate.getSeconds());
|
||||||
|
getElement("#display_timer").textContent = `${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTimer = () => {
|
||||||
|
let minutes = NaN;
|
||||||
|
while (!(minutes > 0 && minutes < 10)) {
|
||||||
|
minutes = parseInt(prompt("Please enter 1 to 10 minutes."));
|
||||||
|
}
|
||||||
|
endTime = new Date();
|
||||||
|
endTime.setMinutes(endTime.getMinutes() + minutes);
|
||||||
|
|
||||||
|
// display timer and then start interval for timer
|
||||||
|
displayTimer();
|
||||||
|
timer = setInterval(displayTimer, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// attach event handler for timer button
|
||||||
|
getElement("#start_timer").addEventListener("click", startTimer);
|
||||||
|
});
|
||||||
53
book_apps/ch08/trivia/index.html
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My Trivia</title>
|
||||||
|
<link href="main.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My Trivia</h1>
|
||||||
|
<div id="question">
|
||||||
|
<p>You have <span id="seconds">10</span> seconds to answer.</p>
|
||||||
|
<label>What is the capitol of Vermont? </label>
|
||||||
|
<input type="radio" name="city" id="bur" value="Burlington">Burlington
|
||||||
|
<input type="radio" name="city" id="mont" value="Montpelier">Montpelier
|
||||||
|
<input type="radio" name="city" id="con" value="Concord">Concord
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let timer = null;
|
||||||
|
const div = document.querySelector("#question");
|
||||||
|
|
||||||
|
const updateTime = () => {
|
||||||
|
const span = document.querySelector("#seconds");
|
||||||
|
const seconds = parseInt(span.textContent) - 1;
|
||||||
|
|
||||||
|
if (seconds == 0) {
|
||||||
|
clearInterval(timer);
|
||||||
|
div.textContent = "Time's up!!";
|
||||||
|
} else {
|
||||||
|
span.textContent = seconds;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processAnswer = evt => {
|
||||||
|
clearInterval(timer);
|
||||||
|
if (evt.currentTarget.value === "Montpelier") {
|
||||||
|
div.textContent = "Correct!! The capitol of Vermont is Montpelier.";
|
||||||
|
} else {
|
||||||
|
div.textContent = "Sorry - the capitol of Vermont is Montpelier.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
timer = setInterval(updateTime, 1000);
|
||||||
|
|
||||||
|
const options = div.querySelectorAll("input");
|
||||||
|
for (let opt of options) {
|
||||||
|
opt.addEventListener("click", processAnswer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
12
book_apps/ch08/trivia/main.css
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 520px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
38
book_apps/ch09/future_value/calc_future_value.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// function that simulates an external service that is sometimes down
|
||||||
|
function getCurrentInterestRate() {
|
||||||
|
const random = Math.ceil(Math.random() * 10);
|
||||||
|
if (random === 1) {
|
||||||
|
throw new Error("Interest Rate API is down.");
|
||||||
|
} else {
|
||||||
|
return 3.9;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function calcFutureValue(investment, years) {
|
||||||
|
// convert to numeric values
|
||||||
|
investment = parseFloat(investment);
|
||||||
|
years = parseInt(years);
|
||||||
|
|
||||||
|
// throw error if investment and years aren't numbers greater than zero
|
||||||
|
if (isNaN(investment) || investment <= 0) {
|
||||||
|
throw new Error("Investment amount must be a number greater than zero.");
|
||||||
|
}
|
||||||
|
if (isNaN(years) || years <= 0) {
|
||||||
|
throw new Error("Years must be a number greater than zero.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get interest rate - throw error if service returns an error
|
||||||
|
let rate = 0;
|
||||||
|
try {
|
||||||
|
rate = getCurrentInterestRate();
|
||||||
|
} catch(e) {
|
||||||
|
throw new Error("Unable to calculate future value. " + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no errors, calculate and return future value
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 1; i <= years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
}
|
||||||
|
return Number(futureValue.toFixed(2));
|
||||||
|
};
|
||||||
31
book_apps/ch09/future_value/future_value.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
padding-left: 1em;
|
||||||
|
width: 600px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8em;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
height: 1.5em;
|
||||||
|
width: 10em;
|
||||||
|
margin: 0 0.5em 1em 0;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
}
|
||||||
|
input[type="submit"], input[type="reset"] {
|
||||||
|
height: 2em;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
span, #message {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
48
book_apps/ch09/future_value/future_value.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const clearMessages = () => {
|
||||||
|
getElement("#investment").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#years").nextElementSibling.textContent = "*";
|
||||||
|
getElement("#message").textContent = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const form = getElement("form");
|
||||||
|
form.noValidate = true;
|
||||||
|
|
||||||
|
for (let element of form.elements) {
|
||||||
|
element.addEventListener("invalid", evt => {
|
||||||
|
const elem = evt.currentTarget;
|
||||||
|
const span = elem.nextElementSibling;
|
||||||
|
if (span) span.textContent = elem.validationMessage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener("submit", evt => {
|
||||||
|
clearMessages();
|
||||||
|
evt.preventDefault(); // prevent form submission in all cases
|
||||||
|
|
||||||
|
// get future value text box and clear any previous value
|
||||||
|
const fv = getElement("#future_value");
|
||||||
|
fv.value = "";
|
||||||
|
|
||||||
|
// if form is valid, display future value or error message
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
try {
|
||||||
|
const investment = getElement("#investment").value;
|
||||||
|
const years = getElement("#years").value;
|
||||||
|
fv.value = calcFutureValue(investment, years);
|
||||||
|
} catch(e) {
|
||||||
|
const msg = `${e.name}: ${e.message}`;
|
||||||
|
getElement("#message").textContent = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getElement("#clear").addEventListener("click", () => {
|
||||||
|
clearMessages();
|
||||||
|
getElement("#investment").focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
35
book_apps/ch09/future_value/index.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
<link rel="stylesheet" href="future_value.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="#" method="get">
|
||||||
|
<h1>The Future Value Calculator</h1>
|
||||||
|
|
||||||
|
<label for="investment">Total investment:</label>
|
||||||
|
<input type="number" id="investment"
|
||||||
|
min="0" max="1000" step="100" required autofocus>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="years">Number of years:</label>
|
||||||
|
<input type="number" id="years" min="0" max="10" step="1" required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label>Future value:</label>
|
||||||
|
<input type="text" id="future_value" disabled><br>
|
||||||
|
|
||||||
|
<label></label>
|
||||||
|
<input type="submit" id="calculate" value="Calculate">
|
||||||
|
<input type="reset" id="clear" value="Clear">
|
||||||
|
|
||||||
|
<p id="message"></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="calc_future_value.js"></script>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
17
book_apps/ch09/register/confirm.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Thanks for registering with us!</h1>
|
||||||
|
<p>You should get a confirmation email shortly.</p>
|
||||||
|
<p><a href="index.html">Go back</a></p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
book_apps/ch09/register/index.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Register for an Account</h1>
|
||||||
|
<form action="confirm.html" method="post">
|
||||||
|
<label for="email_address">E-Mail:</label>
|
||||||
|
<input type="email" name="email_address" id="email_address"
|
||||||
|
placeholder="name@domain.com" autocomplete="off"
|
||||||
|
autofocus required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="dob">DOB:</label>
|
||||||
|
<input type="date" name="dob" id="dob" required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="phone">Mobile Phone:</label>
|
||||||
|
<input type="tel" name="phone" id="phone"
|
||||||
|
autocomplete="off"
|
||||||
|
pattern="\d{3}[\-]\d{3}[\-]\d{4}"
|
||||||
|
title="Must be nnn-nnn-nnnn" required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="country">Country:</label>
|
||||||
|
<select name="country" id="country" required>
|
||||||
|
<option value="">Select a country</option>
|
||||||
|
<option>USA</option>
|
||||||
|
<option>Canada</option>
|
||||||
|
<option>Mexico</option>
|
||||||
|
</select>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label>Contact me by:</label>
|
||||||
|
<input type="radio" name="contact" id="text"
|
||||||
|
value="text" checked>Text
|
||||||
|
<input type="radio" name="contact" id="email"
|
||||||
|
value="email">Email
|
||||||
|
<input type="radio" name="contact" id="none"
|
||||||
|
value="none">Don't contact me<br>
|
||||||
|
|
||||||
|
<label>Terms of Service:</label>
|
||||||
|
<input type="checkbox" name="terms" id="terms"
|
||||||
|
value="yes" required>I accept
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label> </label>
|
||||||
|
<input type="submit" id="register" value="Register">
|
||||||
|
<input type="reset" id="reset_form" value="Reset">
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
book_apps/ch09/register/register.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input, select {
|
||||||
|
margin: 0 0.5em 1em 1em;
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 13em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
17
book_apps/ch09/register_2.0/confirm.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Thanks for registering with us!</h1>
|
||||||
|
<p>You should get a confirmation email shortly.</p>
|
||||||
|
<p><a href="index.html">Go back</a></p>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
62
book_apps/ch09/register_2.0/index.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Registration</title>
|
||||||
|
<link rel="stylesheet" href="register.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Register for an Account</h1>
|
||||||
|
<form action="confirm.html" method="post">
|
||||||
|
<label for="email_address">E-Mail:</label>
|
||||||
|
<input type="email" name="email_address" id="email_address"
|
||||||
|
placeholder="name@domain.com" autocomplete="off"
|
||||||
|
autofocus required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="dob">DOB:</label>
|
||||||
|
<input type="date" name="dob" id="dob"
|
||||||
|
title="You must be at least 13 years old." required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="phone">Mobile Phone:</label>
|
||||||
|
<input type="tel" name="phone" id="phone"
|
||||||
|
placeholder="nnn-nnn-nnnn" autocomplete="off"
|
||||||
|
pattern="\d{3}[\-]\d{3}[\-]\d{4}"
|
||||||
|
title="Must be nnn-nnn-nnnn format." required>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label for="country">Country:</label>
|
||||||
|
<select name="country" id="country"
|
||||||
|
title="Please select a country." required>
|
||||||
|
<option value="">Select a country</option>
|
||||||
|
<option>USA</option>
|
||||||
|
<option>Canada</option>
|
||||||
|
<option>Mexico</option>
|
||||||
|
</select>
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label>Contact me by:</label>
|
||||||
|
<input type="radio" name="contact" id="text"
|
||||||
|
value="text" checked>Text
|
||||||
|
<input type="radio" name="contact" id="email"
|
||||||
|
value="email">Email
|
||||||
|
<input type="radio" name="contact" id="none"
|
||||||
|
value="none">Don't contact me<br>
|
||||||
|
|
||||||
|
<label>Terms of Service:</label>
|
||||||
|
<input type="checkbox" name="terms" id="terms" required
|
||||||
|
title="Please accept the terms of service.">I accept
|
||||||
|
<span>*</span><br>
|
||||||
|
|
||||||
|
<label> </label>
|
||||||
|
<input type="submit" id="register" value="Register">
|
||||||
|
<input type="reset" id="reset_form" value="Reset">
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<script src="register.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
29
book_apps/ch09/register_2.0/register.css
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 640px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input, select {
|
||||||
|
margin: 0 0.5em 1em 1em;
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
width: 13em;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
color: red;
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
||||||
52
book_apps/ch09/register_2.0/register.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
const clearMessages = form => {
|
||||||
|
for (let element of form.elements) {
|
||||||
|
const span = element.nextElementSibling;
|
||||||
|
if (span) span.textContent = "*";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const form = getElement("form");
|
||||||
|
|
||||||
|
// turn off default HTML validation messages
|
||||||
|
form.noValidate = true;
|
||||||
|
|
||||||
|
// attach invalid event handler for form controls
|
||||||
|
for (let element of form.elements) {
|
||||||
|
element.addEventListener("invalid", evt => {
|
||||||
|
const elem = evt.currentTarget;
|
||||||
|
const msg = elem.title ? elem.title : elem.validationMessage;
|
||||||
|
|
||||||
|
const span = elem.nextElementSibling;
|
||||||
|
if (span) span.textContent = msg;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener("submit", evt => {
|
||||||
|
clearMessages(form);
|
||||||
|
|
||||||
|
// do custom age validation
|
||||||
|
const dobElement = getElement("#dob");
|
||||||
|
const dob = new Date(dobElement.value);
|
||||||
|
|
||||||
|
const limit = new Date();
|
||||||
|
limit.setFullYear(limit.getFullYear() - 13);
|
||||||
|
|
||||||
|
const msg = (dob > limit) ? dobElement.title: "";
|
||||||
|
dobElement.setCustomValidity(msg);
|
||||||
|
|
||||||
|
// validate form
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
evt.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getElement("#reset_form").addEventListener("click", () => {
|
||||||
|
clearMessages(form);
|
||||||
|
getElement("#email_address").focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
25
book_apps/ch10/future_value/future_value.css
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: white;
|
||||||
|
margin: 1em auto;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0 2em 1em;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 1em;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: cornflowerblue;
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 11em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
74
book_apps/ch10/future_value/future_value.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
function getElement(selector) {
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNotValid(val) {
|
||||||
|
if (isNaN(val) || val <= 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcFutureValue(investment, rate, years) {
|
||||||
|
let futureValue = investment;
|
||||||
|
for (let i = 0; i < years; i++) {
|
||||||
|
futureValue += futureValue * rate / 100;
|
||||||
|
}
|
||||||
|
return futureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcButtonClick() {
|
||||||
|
// clear any previous calculation
|
||||||
|
getElement("#future_value").value = "";
|
||||||
|
|
||||||
|
// get values user entered in textboxes
|
||||||
|
const investment = parseFloat(getElement("#investment").value);
|
||||||
|
const rate = parseFloat(getElement("#rate").value);
|
||||||
|
const years = parseInt(getElement("#years").value);
|
||||||
|
|
||||||
|
// check user entries and set error message
|
||||||
|
let errorMsg = "";
|
||||||
|
if (isNotValid(investment)) {
|
||||||
|
errorMsg += "Investment amount must be a positive number.\n";
|
||||||
|
getElement("#investment").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(rate)) {
|
||||||
|
errorMsg += "Interest rate must be a positive number.\n";
|
||||||
|
getElement("#rate").focus();
|
||||||
|
}
|
||||||
|
if (isNotValid(years)) {
|
||||||
|
errorMsg += "Number of years must be a positive number.";
|
||||||
|
getElement("#years").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user entries are valid
|
||||||
|
if (errorMsg == "") {
|
||||||
|
// save user entries
|
||||||
|
localStorage.investment = investment;
|
||||||
|
localStorage.rate = rate;
|
||||||
|
localStorage.years = years;
|
||||||
|
|
||||||
|
// calculate and display future value
|
||||||
|
const futureValue = calcFutureValue(investment, rate, years);
|
||||||
|
getElement("#future_value").value = futureValue.toFixed(2);
|
||||||
|
} else {
|
||||||
|
// display error message
|
||||||
|
alert(errorMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
// load user entries
|
||||||
|
getElement("#investment").value = localStorage.investment ?? "";
|
||||||
|
getElement("#rate").value = localStorage.rate ?? "";
|
||||||
|
getElement("#years").value = localStorage.years ?? "";
|
||||||
|
|
||||||
|
// attach the event handler
|
||||||
|
getElement("#calculate").addEventListener("click", calcButtonClick);
|
||||||
|
|
||||||
|
// set focus on first text box on initial load
|
||||||
|
getElement("#investment").focus();
|
||||||
|
});
|
||||||
33
book_apps/ch10/future_value/index.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Future Value Calculator</title>
|
||||||
|
<link rel="stylesheet" href="future_value.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>The Future Value Calculator</h1>
|
||||||
|
<div>
|
||||||
|
<label for="investment">Total investment:</label>
|
||||||
|
<input type="text" id="investment">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="Rate">Annual interest rate:</label>
|
||||||
|
<input type="text" id="rate">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="years">Number of years</label>
|
||||||
|
<input type="text" id="years">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Future value:</label>
|
||||||
|
<input type="text" id="future_value" disabled>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label> </label>
|
||||||
|
<input type="button" id="calculate" value="Calculate">
|
||||||
|
</div>
|
||||||
|
<script src="future_value.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
24
book_apps/ch10/hit_counter/hit_counter.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getElement = selector => document.querySelector(selector);
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
|
||||||
|
// add to existing hits
|
||||||
|
let localHits = localStorage.hits ?? "0"; // get hits
|
||||||
|
localHits = Number(localHits) + 1; // increment by 1
|
||||||
|
localStorage.hits = localHits; // set hits
|
||||||
|
|
||||||
|
let sessionHits = sessionStorage.hits ?? "0";
|
||||||
|
sessionHits = Number(sessionHits) + 1;
|
||||||
|
sessionStorage.hits = sessionHits;
|
||||||
|
|
||||||
|
// display hits
|
||||||
|
console.log("Hits for this browser: " + localStorage.hits);
|
||||||
|
console.log("Hits for this session: " + sessionStorage.hits);
|
||||||
|
|
||||||
|
// display hits
|
||||||
|
getElement("#browser_hits").textContent = localStorage.hits ?? "1";
|
||||||
|
getElement("#session_hits").textContent = sessionStorage.hits ?? "1";
|
||||||
|
|
||||||
|
});
|
||||||
15
book_apps/ch10/hit_counter/index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Hit Counter</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hit Counter</h1>
|
||||||
|
<p>Hits for this browser: <span id="browser_hits"></span></p>
|
||||||
|
<p>Hits for this session: <span id="session_hits"></span></p>
|
||||||
|
<script src="hit_counter.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||