Skip to content

Write JavaScript for a web page

You will connect an external JavaScript file to a web page and write a small program that calculates and reports a project’s completion status in the browser console.

JavaScript adds behavior to a web page. Before you connect behavior to buttons, forms, and other page elements, you need to know how the browser runs a script and how JavaScript represents data, calculates results, makes decisions, and reuses instructions.

What you will practice

  • Load an external JavaScript file with a script element and confirm that it runs in the browser.
  • Use string, number, and boolean values with const and let variable declarations.
  • Combine values with arithmetic, comparison, and template-literal expressions.
  • Use if and else if to select behavior from a boolean condition.
  • Define and call a function with parameters and a return value.
  • Use console output and error messages to trace and debug a small program.
  • New: The browser’s JavaScript runtime, external scripts, statements, expressions, primitive values, variables, operators, template literals, conditional branches, functions, parameters, return values, strict mode, and console debugging.
  • Reused: HTML document structure, relative file paths, VS Code, browser developer tools, saving files, reloading a page, and reading exact error locations.

Starting point

Before you start

  • The completed Level 1 learning goals, including HTML, CSS, local project folders, browser developer tools, and the solo Git workflow.
  • VS Code and a current version of Edge, Chrome, or Firefox.
  • Permission to create a new folder and two files on your computer.
  • No starter files are required.
Current state
You do not have a Level 2 JavaScript practice workspace yet.
First action
Create a folder named web-interaction-lessons, open it in VS Code, and create index.html and app.js inside it.
First checkpoint
The VS Code Explorer shows index.html and app.js at the same level inside web-interaction-lessons.
Help trigger
Use the recovery note or ask for help if the browser displays JavaScript as page text, the Console does not show your saved message after a reload, or an error remains after you check the file and line named in the error.

You have completed the lesson when:

  • web-interaction-lessons contains index.html and app.js;
  • index.html loads app.js through an external script element with defer;
  • the Console shows Community event page and 60% complete. Continue with required tasks. in that order;
  • the program uses string, number, and boolean values;
  • stable project data uses const, while the status message uses let because the program can assign it a different value;
  • a function receives completed and total task counts and returns a percentage;
  • an if and else if statement select the blocked or complete status when either condition is true;
  • changing the test data selects each required status without changing the decision code; and
  • the final version runs without a JavaScript error in the Console.

JavaScript needs an execution environment. An execution environment supplies the rules and features that make code run. In this lesson, the browser is the execution environment. Later JavaScript work can also run in environments such as Node.js.

Add this document to index.html:

web-interaction-lessons/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Project status</title>
<script src="app.js" defer></script>
</head>
<body>
<main>
<h1>Project status</h1>
<p>Open the browser Console to read the current status.</p>
</main>
</body>
</html>

The script element connects the two files:

  • src="app.js" gives the relative path from index.html to the JavaScript file beside it.
  • defer lets the browser continue parsing the HTML before it runs the external script.
  • The element uses both an opening and a closing tag. script is not a void element.

This page puts the script in head and uses defer. Keep this loading method throughout the web-interaction lessons so the project has one predictable script-loading pattern.

Add this code to app.js:

web-interaction-lessons/app.js
"use strict";
console.log("JavaScript is connected.");

"use strict"; is a directive. It enables strict mode for this classic script. Strict mode turns some silent mistakes into errors, which makes those mistakes easier to find.

console.log("JavaScript is connected."); sends a value to the browser Console. It does not add visible content to the web page.

  1. Save index.html and app.js.
  2. Open index.html in Edge, Chrome, or Firefox.
  3. Open the browser developer tools. You can right-click the page, select Inspect, and then select Console.
  4. Reload the page while the Console is open.
  5. Find JavaScript is connected. in the Console.

The Console can contain messages from the browser, an extension, or developer tools. Find your exact message before you treat the connection as confirmed.

  • If the page shows the JavaScript code as text, confirm that the code is in app.js and that the browser opened index.html.
  • If no message appears, save both files, reload the page, and confirm that src="app.js" matches the exact file name.
  • If the Console shows a red error, open the file-and-line link at the right side of the error. Compare that line with the lesson code.
  • If the browser cannot find app.js, confirm that index.html and app.js are in the same folder.

Checkpoint: The external script runs

What now works
The browser renders the Project status page, and the Console shows JavaScript is connected. after each reload.
Files changed
index.html, app.js
What remains
Represent the project data, calculate a result, and select a status message.
Next action
Return to app.js and replace the first console.log call with the project-data declarations in the next section.
If it does not work
Check the saved file names, the script src path, and the first red Console error from top to bottom.

JavaScript source code contains several kinds of parts. These terms help you describe where a result or error comes from.

Term Example Purpose
Value 3 Represents data in the program
Expression completedTasks / totalTasks Produces a value
Statement const completedTasks = 3; Performs an action, such as declaring a variable
Code block { statusMessage = "Ready for review."; } Groups statements for a function or condition
Comment // Stable project data Leaves information for a person reading the source

A semicolon ends each statement in this lesson. JavaScript can insert some semicolons automatically, but explicit semicolons keep the boundary visible while you learn the syntax.

Replace the first console.log call in app.js with these declarations:

web-interaction-lessons/app.js
"use strict";
// Stable project data
const projectName = "Community event page";
const completedTasks = 3;
const totalTasks = 5;
const hasBlockingIssue = false;
console.log(projectName);
console.log(completedTasks);
console.log(totalTasks);
console.log(hasBlockingIssue);

The program now uses three primitive value types:

Type Lesson examples Use
String "Community event page" Text
Number 3 and 5 Numeric data and calculations
Boolean false One of two logical values: true or false

JavaScript has more value types. These three types are enough for the required program. Later lessons introduce structured values when the interface needs to manage several related records.

A variable binds a value to an identifier. An identifier is the name that the code uses to refer to that value, such as projectName.

Use const when the binding will not receive a different value after its declaration. All four project-data bindings remain stable during one run, so they use const.

Use let when the program must assign a different value to the same binding. The status message will use let later because a condition can replace its initial value.

Use names that describe the data. JavaScript identifiers are case-sensitive, so projectName and projectname are different identifiers.

Add these temporary lines at the end of app.js:

Temporary type checks
console.log(typeof projectName);
console.log(typeof completedTasks);
console.log(typeof hasBlockingIssue);

Save the file and reload the page. The last three Console results are string, number, and boolean.

The typeof operator produces a string that identifies the value’s type. Remove the three temporary lines after you confirm the results. Keep the four project-data console.log calls for the next check.

The Console must show these values in this order:

Community event page
3
5
false

Change completedTasks from 3 to 4, save, reload, and confirm that the second Console value changes. Restore it to 3 before you continue.

An operator combines or examines values. The calculation uses arithmetic operators:

(completedTasks / totalTasks) * 100

The division operator / calculates the completed part of the total. The multiplication operator * converts that part to a percentage. Parentheses make the intended grouping explicit.

Put the calculation inside a function. Add this function after the four project-data declarations and before the console.log calls:

Add after the project-data declarations
function calculateCompletion(completed, total) {
if (total === 0) {
return 0;
}
return Math.round((completed / total) * 100);
}

A function groups instructions under one name. The function does not calculate a result until the program calls it.

Read the function from the outside in:

  • calculateCompletion is the function name.
  • completed and total are parameters. They are local identifiers for the input values that one call supplies.
  • total === 0 is a comparison expression. The strict equality operator === produces true when the two values have the same type and value.
  • The first return handles a zero total before division. This guard prevents the program from reporting an unusable division result.
  • Math.round(...) calls a method on JavaScript’s built-in Math object and returns the calculation rounded to the nearest integer.
  • The final return sends that integer back to the code that called the function.

Add this function call after the function declaration:

Add after calculateCompletion
const completionPercentage = calculateCompletion(
completedTasks,
totalTasks,
);

The values completedTasks and totalTasks are arguments. The call supplies them to the function’s completed and total parameters. The returned number becomes the value of completionPercentage.

Replace the four console.log calls with these two calls:

Replace the project-data console calls
console.log(projectName);
console.log(completionPercentage);

Save app.js and reload the page. The Console now shows:

Community event page
60

For the current data, the program evaluates the expression in this order:

  1. The call assigns 3 to completed and 5 to total for this function run.
  2. total === 0 produces false, so the first return does not run.
  3. 3 / 5 produces 0.6.
  4. 0.6 * 100 produces 60.
  5. Math.round(60) produces 60.
  6. return sends 60 back to the call.
  7. completionPercentage receives 60.

Checkpoint: The function returns a calculated result

What now works
The Console shows Community event page followed by 60, and changing completedTasks changes the calculated percentage after a reload.
Files changed
app.js
What remains
Convert the number into a status message and select special messages for blocked and complete work.
Next action
Keep completedTasks at 3 and add the status-message code after the completionPercentage declaration.
If it does not work
Log completedTasks, totalTasks, and completionPercentage separately. Check the argument order and confirm that calculateCompletion returns the Math.round result.

A conditional statement runs a code block only when its condition produces true.

Add this code after the completionPercentage declaration and before the final console.log calls:

Add before the console output
let statusMessage = `${completionPercentage}% complete. Continue with required tasks.`;
if (hasBlockingIssue) {
statusMessage = "Blocked: resolve the current issue.";
} else if (completionPercentage === 100) {
statusMessage = "Ready for review.";
}

Replace the second console.log argument so the final output uses statusMessage:

Final console output
console.log(projectName);
console.log(statusMessage);

The first status uses a template literal. Backticks mark the complete string. ${completionPercentage} evaluates the expression and inserts its value into the text.

The code declares statusMessage with let because a true condition assigns a different string to that binding.

The branches run in order:

  1. if (hasBlockingIssue) checks the boolean value directly.
  2. When that value is true, the program assigns the blocked message and skips the else if branch.
  3. When the first condition is false, else if compares the percentage with 100.
  4. When both conditions are false, neither block runs, so the initial progress message remains.

Change only the project data at the top of app.js. Save and reload after each test.

Test data Expected second Console message Selected path
completedTasks = 3, totalTasks = 5, hasBlockingIssue = false 60% complete. Continue with required tasks. Initial message remains
completedTasks = 5, totalTasks = 5, hasBlockingIssue = false Ready for review. else if block
completedTasks = 3, totalTasks = 5, hasBlockingIssue = true Blocked: resolve the current issue. First if block

The blocked test also shows that branch order is a product decision. A project with all tasks complete but a blocking issue still reports Blocked because the program checks that condition first.

Restore the first row’s data before the final verification.

  • If the Console shows ReferenceError, check the identifier spelling and letter case. Also confirm that the declaration appears before the code that uses it.
  • If the Console shows SyntaxError, check the named line and the line before it for a missing quote, backtick, parenthesis, brace, or semicolon.
  • If the message contains NaN, log both function arguments. Confirm that both arguments are numbers and that both declarations exist.
  • If every test shows the same status, log hasBlockingIssue and completionPercentage before the conditional statement. Confirm that you saved and reloaded after each data change.
  • If 60 appears instead of the full message, confirm that the second console.log uses statusMessage.

Your final app.js must match this structure:

web-interaction-lessons/app.js — required result
"use strict";
// Stable project data
const projectName = "Community event page";
const completedTasks = 3;
const totalTasks = 5;
const hasBlockingIssue = false;
function calculateCompletion(completed, total) {
if (total === 0) {
return 0;
}
return Math.round((completed / total) * 100);
}
const completionPercentage = calculateCompletion(
completedTasks,
totalTasks,
);
let statusMessage = `${completionPercentage}% complete. Continue with required tasks.`;
if (hasBlockingIssue) {
statusMessage = "Blocked: resolve the current issue.";
} else if (completionPercentage === 100) {
statusMessage = "Ready for review.";
}
console.log(projectName);
console.log(statusMessage);

The code has one data flow:

project data
-> calculateCompletion arguments
-> returned percentage
-> conditional checks
-> selected statusMessage
-> Console output

The arrows show which result becomes the next input. They are an explanation of the program, not JavaScript syntax.

Self-check

Complete these checks against the required result.

  1. Confirm that index.html and app.js are saved in the same folder and that the script element uses src="app.js" and defer.
  2. Reload the page and confirm that the Console shows Community event page and 60% complete. Continue with required tasks. in that order.
  3. Point to one string, number, and boolean value in app.js and state what each value represents.
  4. Explain why the project-data bindings use const and statusMessage uses let.
  5. Trace the values 3 and 5 from the function arguments through the parameters to the returned value 60.
  6. Set completedTasks to 5 and confirm that the complete branch reports Ready for review.
  7. Set hasBlockingIssue to true and confirm that the first branch reports the blocked message.
  8. Restore completedTasks to 3 and hasBlockingIssue to false, then confirm that no red JavaScript error remains in the Console.

Checkpoint: The JavaScript result is complete

What now works
The external script calculates a percentage, selects the correct status for all three test cases, and reports the restored 60% result without a Console error.
Files changed
index.html, app.js
What remains
Connect JavaScript behavior to elements and user events in the next lesson.
Next action
Keep this workspace and its restored project data. Open the DOM-events lesson when you are ready to make the page respond to user input.
If it does not work
Compare app.js with the complete program, then fix the first Console error before you test later behavior.

Reference pattern: choose a random result within known boundaries

Section titled “Reference pattern: choose a random result within known boundaries”

Some later projects need an unpredictable result. Use this section when a project asks for a percentage chance or one random item from an array. It is a reference pattern, not another requirement for the project-status program above.

JavaScript’s Math.random() method returns one number that is:

  • greater than or equal to 0; and
  • less than 1.

The result can be 0, but it cannot be 1. Read the official references for Math.random() and Math.floor() when you need the complete API details.

This neutral example gives a review task a 25% chance of being selected:

Percentage-chance example
const selectionPercentage = 25;
const selectionThreshold = selectionPercentage / 100;
const chanceValue = Math.random();
const shouldSelectReview = chanceValue < selectionThreshold;

25 / 100 produces the threshold 0.25. The comparison produces one boolean value:

chanceValue Comparison shouldSelectReview
0 0 < 0.25 true
0.249 0.249 < 0.25 true
0.25 0.25 < 0.25 false
0.999 0.999 < 0.25 false

The boundary belongs to the product rule. If the rule says 25%, keep the same percentage and comparison until you deliberately change that rule.

Use a separate random value when a successful chance decision must select one item:

Bounded array-selection example
const reviewAreas = ["keyboard", "layout", "console"];
const selectionValue = Math.random();
const reviewIndex = Math.floor(selectionValue * reviewAreas.length);
const selectedReviewArea = reviewAreas[reviewIndex];

The array contains three items, so its valid indexes are 0, 1, and 2. Multiplication changes the range from 0–less-than-1 to 0–less-than-3. Math.floor() rounds each value down to the nearest integer:

selectionValue Value before Math.floor() reviewIndex
0 0 0
0.499 1.497 1
0.999 2.997 2

Because Math.random() cannot return 1, this calculation cannot produce index 3. If the array length changes, the same calculation uses the new length and still selects a valid index.

Keep the two random responsibilities separate

Section titled “Keep the two random responsibilities separate”

Use this order when one action needs both decisions:

  1. Create one random value for the percentage-chance decision.
  2. Stop if the chance decision fails.
  3. Create a second random value to select one valid array index.
  4. Store the selected item in the program’s state or another stable variable.
  5. Render from that stored result.

Do not generate either random value inside a render function. A render can run again after resizing, focus restoration, or another state change. The selected result must remain stable until a user action or product rule deliberately replaces it.

Temporarily replace chanceValue with 0.249 and then 0.25. Confirm that the two values select different sides of the 25% boundary. Restore Math.random() afterward.

Temporarily replace selectionValue with 0, 0.499, and 0.999. Confirm that the example selects indexes 0, 1, and 2. Restore Math.random() and confirm that no Console error remains.

Console errors name the type of failure and usually identify a file and line. Start with the first error because one early syntax error can prevent later statements from running.

Error type Typical meaning in this lesson First check
SyntaxError The browser cannot parse part of the source Check punctuation on the named line and the line before it
ReferenceError The code uses an identifier that is not available Check spelling, letter case, and declaration order
TypeError An operation does not work with the current value Check the value and the operation named in the message

Do not delete working sections at random. Read the first error, inspect its location, correct one cause, save, and reload. This keeps the test connected to one hypothesis.

The required lesson is complete when the browser runs the external script, the final Console result is restored, all three conditional paths pass their tests, and you can trace the data from declarations to output.

Continue to Respond to user input with DOM events to select page elements and run JavaScript when a user activates a control.

If you stop here, leave yourself this resume note: The project-status calculation works in the Console. Next, open the DOM-events lesson and connect the working JavaScript to page elements and a user action.