Unit 2 home
FSWD · MERN LAB 5 · EX.4 UI23PC511CS · LAB poshtik-campus/ CHECKS EVERY ORDER
LAB 5 · P 1/10
LAB 5 · EX.4 PART 2 OF 2 UNIT 2 · JAVASCRIPT ONE 2-HOUR SESSION

PART 1 · OPENER · 2 MIN

Your site defends itself.

In Lab 4, script.js learned to read the order form. Today it does two real jobs. It builds the ten dish cards from data. It refuses a bad order and shows red messages on the page.

R-23 SYLLABUS · FSWD LAB · PROGRAMMING EXERCISE 4

“Validation of Static Website using JavaScript.”

Two hours, ten parts

#PARTMINENDS AT
1Opener20:02
2Prelab theory — five questions110:13
3Model answers — mark your own60:19
4Prelab coding — four drills280:47
5Walkthrough and project setup90:56
6Exercise 1 — render the menu from data181:14
7Exercise 1 solution81:22
8Exercise 2 — check every order211:43
9Exercise 2 solution81:51
10Debrief, commit and push92:00
Total1202:00

By 2:00 you can…

Build page content from an array with forEach, createElement and appendChild
Stop a form from reloading and check it with your own code
Clean input before checking it: trim text, convert numbers, match a phone pattern
Show each problem next to its field, and place a good order without a reload
Start the clock in the top bar now.

It counts down the two hours. Behind after Lab 4? Part 5 shows how to catch up.

PART 2 · PRELAB THEORY · 11 MIN

Five questions, in your notebook.

Q1 to Q3 come with hints. Q4 and Q5 do not. Write all five answers before you open any of them.

Q1 · GUIDED

Why does a submit listener call event.preventDefault() as its first line?

  • What does a browser do with a form when its submit button is pressed?
  • What happens to a red message your script has just written?
  • Why first, and not last?
write yours first

Submitting sends the form and loads the page again. Everything your script wrote on the page is wiped, and so is the Console.

preventDefault() cancels that. Put it first: if a later line crashes, the page still stays, and the error stays visible in the Console.

Q2 · GUIDED

A customer types three spaces as their name. Why does name === "" let it through, and what does .trim() change?

  • Is " " the same string as ""?
  • What does .trim() remove, and from where?
write yours first

" " is three characters long, so it is not equal to "".

.trim() removes spaces from both ends. " " becomes "" and the check refuses it. A real name is saved as "Ravi", not " Ravi ".

Q3 · GUIDED

Why convert the quantity with Number() before you check it?

  • What did typeof print for the quantity box in Lab 4?
  • What does Number.isInteger("3") return?
  • Is "9" > "10" true or false?
write yours first

.value is always a string, even from type="number". Number.isInteger("3") is false, so a valid 3 would be refused. Two strings compare letter by letter, so "9" > "10" is true.

Number() turns "3" into 3. After that, Number.isInteger, < and > check real numbers. "2.5" becomes 2.5, which is not a whole number.

Q4 · ON YOUR OWN

Lab 4 printed the menu with for (let i = 0; i < 3; i = i + 1). The array now holds 10 dishes. Why is poshtikMenu.forEach(…) the better loop?

write yours first

The hard-coded 3 builds only the first 3 of 10 dishes, and nothing warns you.

forEach runs once for every item, whatever the length. An eleventh dish needs no change to the loop.

Q5 · ON YOUR OWN

What does document.createElement("article") give you, and why does nothing appear on the page until appendChild?

write yours first

A new, empty <article> element. It exists only in memory. You can give it a class, text and children, but it is not inside the page.

appendChild puts it inside an element that is already on the page, such as #dish-list. Only then does the browser draw it. Forget it and there is no error, just no card.

PART 3 · MODEL ANSWERS · SELF-MARK · 6 MIN

Mark your own five.

Two marks a question. Take 2 only if your answer holds the idea in the middle column.

Q2 MARKS: YOUR ANSWER SAYS1 MARK: IT ONLY SAYS
Q1Submitting reloads the page and wipes the messages; preventDefault() cancels the reload.“It stops the form.”
Q2" " is not ""; trim removes spaces at both ends, so the empty check catches it.“Trim removes spaces.”
Q3.value is a string; Number.isInteger("3") is false and "9" > "10" is true; Number() fixes both.“Convert it to a number.”
Q4A fixed 3 silently misses dishes 4 to 10; forEach visits every item, whatever the length.“forEach is shorter.”
Q5The new element lives only in memory; appendChild puts it inside the page, and only then is it drawn.“It creates an element.”
Under 6 out of 10? Reread the answers you missed, close them, and say each one aloud once. Then start Part 4.

PART 4 · PRELAB CODING DRILLS · 28 MIN

Four drills, four tiny pages.

Each drill is one HTML file with its script inside. Give each about seven minutes. Unlock a solution only when your page and Console match the expected output.

1
Make the practice folder

On the Desktop, create fswd-practice\lab-05\drills\. Drills never go inside poshtik-campus.

2
Copy your stylesheet

Copy style.css from poshtik-campus into fswd-practice\lab-05\. Each drill links it as ../style.css.

3
Open the folder in VS Code

File, Open Folder, fswd-practice\lab-05. Create each drill file inside drills.

4
Run it

Right-click the file, Open with Live Server. Press F12 and click Console.

One line to leave empty.

Each solution has one highlighted line that loads ../console-tee.js. It feeds the console panel on this page and is not part of the drill. On your machine, leave that line blank so your line numbers still match.

DRILL 1forEach over five dish namesPRACTISES EXERCISE 1
PROBLEM

In drills\drill-1.html, store five dish names in a const array. Use forEach to add each name to <ul id="dish-names"> as an <li> and print it. After the loop, print the total.

INPUT

None.

EXPECTED OUTPUT

A five-item list on the page. Console: the five names, then total dishes: 5.

THE ONE IDEA

forEach runs your function once per item. You never write the count.

Six lines in your Console? Then open it.

drill-1.html · complete fileONE LINE PER PRESS
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Drill 1 · forEach</title>
6 <link rel="stylesheet" href="../style.css">
7</head>
8<body>
9 <h1>Drill 1 · Five dishes, one loop</h1>
10 <ul id="dish-names"></ul>
11
12 <script src="../console-tee.js"></script>
13 <script>
14 const dishNames = ["Jonna Rotte Wrap", "Ragi Sangati Bowl", "Ragi Idli Bowl",
15 "Sprouts Moong Chilla", "Millet Protein Shake"];
16 const list = document.getElementById("dish-names");
17
18 // forEach runs the function once for every item in the array.
19 dishNames.forEach(function (dishName) {
20 const item = document.createElement("li");
21 item.textContent = dishName;
22 list.appendChild(item);
23 console.log(dishName);
24 });
25 console.log("total dishes: " + dishNames.length);
26 </script>
27</body>
28</html>
DRILL 1 RUNNING · CONSOLE BELOW IS THE FRAME'S OWN
file:///C:/Users/student/Desktop/fswd-practice/lab-05/drills/drill-1.html
CONSOLE · F12
Waiting for the drill to load…
DRILL 2One dish as an objectPRACTISES EXERCISE 1
PROBLEM

In drill-2.html, make one dish object with name, price and isMillet. Print each field using dot notation. Then build one sentence with a template literal, show it in <p id="dish-line"> and print it.

INPUT

None.

EXPECTED OUTPUT

Console: name: Ragi Sangati Bowl, price: 55, isMillet: true, then the sentence. The page shows the same sentence.

THE ONE IDEA

Every card in Exercise 1 is built from one object like this, read one field at a time.

Four lines in your Console? Then open it.

drill-2.html · complete fileONE LINE PER PRESS
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Drill 2 · Object literal</title>
6 <link rel="stylesheet" href="../style.css">
7</head>
8<body>
9 <h1>Drill 2 · One dish as an object</h1>
10 <p id="dish-line"></p>
11
12 <script src="../console-tee.js"></script>
13 <script>
14 const dish = { name: "Ragi Sangati Bowl", price: 55, isMillet: true };
15
16 // Dot notation reads one field at a time.
17 console.log("name: " + dish.name);
18 console.log("price: " + dish.price);
19 console.log("isMillet: " + dish.isMillet);
20
21 // A template literal uses backticks and ${ } to drop values into text.
22 const sentence = `${dish.name} costs Rs. ${dish.price}. Millet dish: ${dish.isMillet}.`;
23 document.getElementById("dish-line").textContent = sentence;
24 console.log(sentence);
25 </script>
26</body>
27</html>
DRILL 2 RUNNING · CONSOLE BELOW IS THE FRAME'S OWN
file:///C:/Users/student/Desktop/fswd-practice/lab-05/drills/drill-2.html
CONSOLE · F12
Waiting for the drill to load…
DRILL 3Read and trim a namePRACTISES EXERCISE 2
PROBLEM

In drill-3.html, add a text box #cust-name, a button #greet-btn and <p id="greeting">. On click, read the box with .value.trim(). If it is empty, show Please type your name first. in red and print name is empty. Otherwise show Welcome to Poshtik Campus, Ravi! in black and print name: Ravi.

INPUT

First three spaces. Then Ravi with spaces around it.

EXPECTED OUTPUT

Spaces: the red message and name is empty. Ravi: the welcome line and name: Ravi, with no spaces.

THE ONE IDEA

Trim first, then check. A box holding only spaces is an empty answer.

Both inputs tried? Then open it.

drill-3.html · complete fileONE LINE PER PRESS
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Drill 3 · Read an input</title>
6 <link rel="stylesheet" href="../style.css">
7</head>
8<body>
9 <h1>Drill 3 · Who is ordering?</h1>
10 <input type="text" id="cust-name" placeholder="Your name">
11 <button type="button" id="greet-btn">Say hello</button>
12 <p id="greeting"></p>
13
14 <script src="../console-tee.js"></script>
15 <script>
16 const greeting = document.querySelector("#greeting");
17
18 document.querySelector("#greet-btn").addEventListener("click", function () {
19 const customerName = document.querySelector("#cust-name").value.trim();
20 if (customerName === "") {
21 greeting.textContent = "Please type your name first.";
22 greeting.style.color = "red";
23 console.log("name is empty");
24 } else {
25 greeting.textContent = "Welcome to Poshtik Campus, " + customerName + "!";
26 greeting.style.color = "black";
27 console.log("name: " + customerName);
28 }
29 });
30 </script>
31</body>
32</html>
DRILL 3 RUNNING · TYPE IN THE FRAME, THEN PRESS THE BUTTON
file:///C:/Users/student/Desktop/fswd-practice/lab-05/drills/drill-3.html
CONSOLE · F12
Waiting for the drill to load…
DRILL 4Check a quantity on submitPRACTISES EXERCISE 2
PROBLEM

In drill-4.html, make <form id="qty-form" novalidate> holding a number box #qty and a submit button. On submit, stop the reload and convert the value with Number(). Show valid for a whole number from 1 to 10. Otherwise show Quantity must be a whole number from 1 to 10. Print quantity 3: valid style lines.

INPUT

Try 3, 2.5, 0 and 11.

EXPECTED OUTPUT

3 is valid. The other three show the message. The page never reloads, so the Console keeps all four lines.

THE ONE IDEA

Number() first, then Number.isInteger and the range. preventDefault() keeps the page still.

All four quantities tried? Then open it.

drill-4.html · complete fileONE LINE PER PRESS
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>Drill 4 · Check a quantity</title>
6 <link rel="stylesheet" href="../style.css">
7</head>
8<body>
9 <h1>Drill 4 · How many plates?</h1>
10 <form id="qty-form" novalidate>
11 <label for="qty">Quantity (1 to 10)</label>
12 <input type="number" id="qty">
13 <button type="submit">Check</button>
14 </form>
15 <p id="qty-result"></p>
16
17 <script src="../console-tee.js"></script>
18 <script>
19 const result = document.getElementById("qty-result");
20
21 document.getElementById("qty-form").addEventListener("submit", function (event) {
22 event.preventDefault(); // no page reload
23 const quantity = Number(document.getElementById("qty").value);
24 if (Number.isInteger(quantity) && quantity >= 1 && quantity <= 10) {
25 result.textContent = "valid";
26 } else {
27 result.textContent = "Quantity must be a whole number from 1 to 10.";
28 }
29 console.log("quantity " + quantity + ": " + result.textContent);
30 });
31 </script>
32</body>
33</html>
DRILL 4 RUNNING · TYPE A QUANTITY, THEN PRESS CHECK
file:///C:/Users/student/Desktop/fswd-practice/lab-05/drills/drill-4.html
CONSOLE · F12
Waiting for the drill to load…

PART 5 · WALKTHROUGH + PROJECT SETUP · 9 MIN

Back to poshtik-campus. Check your starting line.

Lab 5 continues the folder Lab 4 left you. Run four checks, then read the map of today's changes.

1
Open the project

In VS Code: File, Open Folder, Desktop\poshtik-campus. You see index.html, menu.html, about.html, style.css, script.js and images.

2
Check the last commit

Open the terminal with Ctrl + ` and run git log --oneline -1. The line ends with Model menu as objects and read order form values.

3
Run the site

Right-click menu.html, Open with Live Server. Press F12, click Console. You see three dish objects and dishes modelled: 3.

4
Try the old button

Type a name, pick a dish, press Check my order. The values print in the Console.

What changes today

FILENOWAFTER EXERCISE 1AFTER EXERCISE 2
menu.html150 lines · ten typed cards · Check my order button82 lines · one empty #dish-list85 lines · four error spans · #order-result · no Check button
script.js76 lines · 3 dishes · click listener107 lines · 10 dishes built into cards · click listener still there110 lines · one submit listener with four checks
style.css81 lines81 lines · untouched94 lines · 13 new lines above @media
A check failed? Catch up in this order.
  1. You pushed Lab 4. On any machine, run git clone https://github.com/<your-username>/poshtik-campus.git. Your own work beats any handout.
  2. Only one Lab 4 step is missing. Open Lab 4 and redo that step now.
  3. Nothing else works (machine lost, or an excused absence). Ask your instructor, then use the rescue pack. Read its README-FIRST.txt before anything else.

Single files: README-FIRST.txt · index.html · menu.html · about.html · style.css · script.js. The photos come only inside the ZIP.

YOUR STARTING FOLDER
poshtik-campus/
index.html
menu.html 150 lines · ten cards typed by hand
about.html
style.css 81 lines
script.js 76 lines · Lab 4's script
images/ logo + ten dish photos
EXERCISE 1 · SPEC ONLY — NO CODE IS GIVEN IN THIS PART

PART 6 · EXERCISE 1 · 18 MIN

Build the ten cards from data.

menu.html repeats one six-line card ten times. Move the facts into poshtikMenu and let one loop build every card.

HOW ONE DISH BECOMES ONE CARD · ONE STEP PER PRESS
poshtikMenu an array of 10 dish objects [0] Jonna Rotte Wrap [1] Sajja Roti Wrap [2] Ragi Sangati Bowl [3] Ragi Idli Bowl [4] Pesarattu [5] Ulava Charu Bowl [6] Gongura Salad [7] Moong Chilla [8] Paneer Bowl [9] Millet Shake forEach one dish per turn turn 1 of 10: dish [0] dish = { name: "Jonna Rotte Wrap", price: 60, photo: "images/…", alt, description } createElement builds the card in memory article.dish img h3 p p photo name text price not on the page yet it exists only in memory appendChild menu.html #dish-list Jonna Rotte Wrap Price: Rs. 60 Sajja Roti Wrap …and 8 more cards repeat for every dish: 10 turns, 10 cards dishes rendered: 10
EXERCISE 1Render the menu from poshtikMenumenu.html + script.js
PROBLEM
  1. Before deleting anything, note each card's photo file, alt text, name, description and price.
  2. In menu.html, replace the ten <article class="dish"> cards with one empty <section id="dish-list"></section>.
  3. In script.js, replace Lab 4's 3-dish array and its for loop with poshtikMenu: 10 objects with name, category, price, isMillet, photo, alt and description. Category is the dish type: Wrap, Bowl, Dosa, Salad, Chilla or Drink. isMillet is true for both wraps, both ragi dishes and the shake.
  4. For each dish, create <article class="dish"> holding an <img> (src, alt, width 180), an <h3> name, a <p> description and a <p> reading Price: Rs. 60. Add the card to #dish-list with appendChild.
  5. After the loop, print dishes rendered: followed by the array's length.
  6. Leave Lab 4's click listener below your code as it is. Exercise 2 replaces it.
INPUT

None. The data lives in script.js.

EXPECTED OUTPUT

The menu looks exactly as before: ten cards, same photos, names, text and prices. Console: dishes rendered: 10. No red errors.

THE ONE IDEA

The page is drawn from data. An eleventh dish is one more object, not six more lines of HTML.

Done when the ten cards are back on your page and Check my order still prints values.

PART 7 · EXERCISE 1 SOLUTION + REAL OUTPUT · 8 MIN

Exercise 1, solved.

Ten cards on your page, or 18 minutes gone? Then open it.

WHERE EXACTLY · menu.html · THE CARDS GO
FILEposhtik-campus\menu.html
FINDline 30, the first <article class="dish"> (Jonna Rotte Wrap), down to line 98, the last </article> (Millet Protein Shake)
DELETElines 30 to 98: click at the start of line 30, Shift+click at the end of line 98, press Delete
INSERTon the now-empty line 30: <section id="dish-list"></section>, indented like the <h2>
NEVERdelete the Today's Healthy Ten heading or the Order Here heading
CHECK82 lines. Line 30 is the section, line 32 is <h2>Order Here</h2>.
WHERE EXACTLY · script.js · THE DATA AND THE LOOP
FILEposhtik-campus\script.js
FINDline 1, the top comment, down to line 25, console.log("dishes modelled:", 3);
DELETElines 1 to 25. The blank line 26 stays.
INSERTat line 1, type lines 1 to 56 from the panel below
NEVERtouch the MPS-02 block below it. It still runs Check my order.
CHECK107 lines. Line 56 prints dishes rendered, line 58 starts /* ===== MPS-02.
menu.html · lines 26–32ONE LINE PER PRESS
26 <main>
27
28 <h2>Today's Healthy Ten</h2>
29
30 <section id="dish-list"></section>
31
32 <h2>Order Here</h2>
script.js · lines 1–57ONE LINE PER PRESS
1/* script.js — Poshtik Campus: builds the menu cards, then checks every order. */
2
3/* ===== EXERCISE 1 · render the menu from data ===== */
4const poshtikMenu = [
5 { name: "Jonna Rotte Wrap", category: "Wrap", price: 60, isMillet: true,
6 photo: "images/jonna-rotte-wrap.png", alt: "Jonna rotte wrap filled with vegetables",
7 description: "Telangana sorghum-flatbread wrap, loaded with crunchy vegetables." },
8 { name: "Sajja Roti Wrap", category: "Wrap", price: 60, isMillet: true,
9 photo: "images/sajja-roti-wrap.png", alt: "Sajja roti wrap with fresh filling",
10 description: "Pearl-millet flatbread wrap — dense, warm, and filling." },
11 { name: "Ragi Sangati Bowl", category: "Bowl", price: 55, isMillet: true,
12 photo: "images/ragi-sangati-bowl.png", alt: "Ragi sangati bowl served hot",
13 description: "Classic Telangana finger-millet mudde bowl, served hot." },
14 { name: "Ragi Idli Bowl", category: "Bowl", price: 50, isMillet: true,
15 photo: "images/ragi-idli-bowl.jpg", alt: "Bowl of soft ragi idlis",
16 description: "Soft steamed finger-millet idlis with chutney." },
17 { name: "Pesarattu with Sprouts", category: "Dosa", price: 55, isMillet: false,
18 photo: "images/pesarattu-with-sprouts.jpg", alt: "Pesarattu dosa topped with sprouts",
19 description: "Andhra green-gram dosa, topped with fresh sprouts." },
20 { name: "Ulava Charu Protein Bowl", category: "Bowl", price: 70, isMillet: false,
21 photo: "images/ulava-charu-protein-bowl.jpg", alt: "Ulava charu protein bowl",
22 description: "Telangana horse-gram stew over a protein-rich grain bowl." },
23 { name: "Gongura Sprouts Salad", category: "Salad", price: 45, isMillet: false,
24 photo: "images/gongura-sprouts-salad.jpg", alt: "Gongura sprouts salad",
25 description: "Tangy Andhra gongura leaves tossed with mixed sprouts." },
26 { name: "Sprouts Moong Chilla", category: "Chilla", price: 50, isMillet: false,
27 photo: "images/sprouts-moong-chilla.jpg", alt: "Sprouts moong chilla on a plate",
28 description: "Savoury moong pancake studded with sprouts." },
29 { name: "Paneer Protein Bowl", category: "Bowl", price: 80, isMillet: false,
30 photo: "images/paneer-protein-bowl.jpg", alt: "Paneer protein bowl",
31 description: "Grilled paneer cubes over greens and grains." },
32 { name: "Millet Protein Shake", category: "Drink", price: 40, isMillet: true,
33 photo: "images/millet-protein-shake.jpg", alt: "Glass of millet protein shake",
34 description: "Cold millet-and-jaggery protein shake." }
35];
36
37const dishList = document.getElementById("dish-list");
38
39// For each dish, build <article class="dish"> holding img, h3, p, p.
40poshtikMenu.forEach(function (dish) {
41 const card = document.createElement("article");
42 card.className = "dish";
43 const photo = document.createElement("img");
44 photo.src = dish.photo;
45 photo.alt = dish.alt;
46 photo.width = 180;
47 const title = document.createElement("h3");
48 title.textContent = dish.name;
49 const blurb = document.createElement("p");
50 blurb.textContent = dish.description;
51 const price = document.createElement("p");
52 price.textContent = "Price: Rs. " + dish.price;
53 card.append(photo, title, blurb, price); // same order as the old cards
54 dishList.appendChild(card); // nothing shows until the card joins the page
55});
56console.log("dishes rendered: " + poshtikMenu.length);
57
REAL FILES · THE FINISHED LAB 5 SITE · CONSOLE IS THE FRAME'S OWN
file:///C:/Users/student/Desktop/poshtik-campus/menu.html
CONSOLE · F12 PRINTED BY THE FRAME'S SCRIPT.JS
Waiting for the frame to load script.js…
Check two things. Ten cards under Today's Healthy Ten, and dishes rendered: 10 as the first console line. The order form in this frame already has Exercise 2 working.
EXERCISE 2 · SPEC ONLY — NO CODE IS GIVEN IN THIS PART

PART 8 · EXERCISE 2 · 21 MIN

Refuse a bad order.

Right now Place order accepts anything: an empty name, a 5-digit phone, quantity 2.5. Make the page check every field and name each problem beside its field.

WHAT HAPPENS WHEN PLACE ORDER IS PRESSED · ONE STEP PER PRESS
Place order submit event fires on the form, not the button event.preventDefault() the page stays: no reload clear old messages 4 error spans + result emptied FOUR CHECKS · ALL FOUR RUN · EACH FAIL WRITES ITS MESSAGE AND ADDS 1 TO problems name trimmed, not "" phone /^[0-9]{10}$/ dish one radio :checked quantity Number(), whole, 1–10 problems > 0 ? YES refuse the order Please enter your name. Phone number must be exactly 10 digits. order refused: 2 problem(s) return: nothing is placed NO place the order Order placed: 2 × Ragi Sangati Bowl for Ravi {customerName: 'Ravi', phone: …, dish: 'Ragi Sangati Bowl', quantity: 2} no reload: the message stays
EXERCISE 2Check the order on submitmenu.html + style.css + script.js
PROBLEM
  1. menu.html: add novalidate to <form>, so your script does the checking, not the browser.
  2. menu.html: add an empty <span class="error"> after the name input (err-name), after the phone input (err-phone), after </fieldset> (err-dish) and after the quantity input (err-qty).
  3. menu.html: remove the Check my order button and its comment. After </form>, add <p id="order-result"></p>.
  4. style.css, above @media: .error is a red 14px block; #order-result is a mint box with a seagreen border, hidden while :empty.
  5. script.js: replace Lab 4's click-listener block with one submit listener on the form. Its first line is event.preventDefault().
  6. Clear the four error spans and #order-result. Then run all four checks, adding 1 to problems for each failure:
    • name, trimmed, not empty: Please enter your name.
    • phone, trimmed, matches /^[0-9]{10}$/: Phone number must be exactly 10 digits.
    • a dish radio is checked: Please pick a dish.
    • quantity after Number() is a whole number from 1 to 10: Quantity must be a whole number from 1 to 10.
  7. Problems found: print order refused: 3 problem(s) with the real count and stop. None: show Order placed: 2 × Ragi Sangati Bowl for Ravi in #order-result and print the order object with customerName, phone, dish and quantity.
INPUT

A Everything empty, quantity left at 1.
B Name of three spaces, phone 98765, any dish, quantity 2.5.
C Ravi, 9876543210, Ragi Sangati Bowl, 2.

EXPECTED OUTPUT

A Messages under name, phone and dish; order refused: 3 problem(s).
B Messages under name, phone and quantity; order refused: 3 problem(s).
C Order placed: 2 × Ragi Sangati Bowl for Ravi and the order object. The page never reloads.

THE ONE IDEA

Check everything, count the problems, decide once. The customer sees every mistake in one go.

PART 9 · EXERCISE 2 SOLUTION + REAL OUTPUT · 8 MIN

Exercise 2, solved.

All three tests from Part 8 tried? Then open it.

menu.html: eight edits, top to bottom

Do them in this order. Each line number is correct at the moment you reach that box.

1 · menu.html · novalidate
FINDline 34, <form>
INSERTchange it to <form novalidate>
NEVERput novalidate on the button; it belongs to the form
CHECKstill 82 lines
2 · menu.html · err-name
FINDline 37, the cust-name input
INSERTclick at the end of line 37, press Enter, type line 38: <span class="error" id="err-name"></span>
NEVERinside the <label> on line 36
CHECK83 lines; line 39 is </p>
3 · menu.html · err-phone
FINDline 42, the cust-phone input
INSERTend of line 42, Enter, line 43: <span class="error" id="err-phone"></span>
NEVERreuse err-name; every id is used once
CHECK84 lines; line 45 is <fieldset>
4 · menu.html · err-dish
FINDline 57, </fieldset>
INSERTend of line 57, Enter, line 58: <span class="error" id="err-dish"></span>
NEVERbetween the radio lines, inside the fieldset
CHECK85 lines; line 59 is <p>
5 · menu.html · err-qty
FINDline 61, the qty input
INSERTend of line 61, Enter, line 62: <span class="error" id="err-qty"></span>
NEVERafter the notes textarea; that field is not checked
CHECK86 lines
6 · menu.html · the old button
FINDlines 70 and 71: the LAB 4, STEP 2 comment and the check-order button
DELETEboth lines
NEVERdelete line 69, the Place order button
CHECK84 lines; line 71 is </form>
7 · menu.html · order-result
FINDline 71, </form>
INSERTend of line 71, Enter, line 72: <p id="order-result"></p>
NEVERinside the form, above </form>
CHECK85 lines; line 74 is </main>
8 · menu.html · the script comment
FINDlines 80 and 81, the comment above <script src="script.js">
INSERTretype both lines as in the panel, so the comment says what the script does now
NEVERmove the script tag; it stays the last line before </body>
CHECK85 lines; line 82 is the script tag
menu.html · complete file, 85 linesONE STEP PER PRESS
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Poshtik Campus — Menu</title>
7 <link rel="stylesheet" href="style.css">
8</head>
9<!-- menu.html · Poshtik Campus menu page.
10 The dish cards and the order form live inside <main> below.
11 Photos come from the images folder; the look comes from style.css. -->
12<body>
13
14 <header id="page-header">
15 <img src="images/poshtik-campus-logo.jpg" alt="Poshtik Campus logo" width="90">
16 <h1>Poshtik Campus Menu</h1>
17 <p>Healthy Food, Healthy Body, Healthy Mind</p>
18 </header>
19
20 <nav>
21 <a href="index.html">Home</a>
22 <a href="menu.html">Menu</a>
23 <a href="about.html">About</a>
24 </nav>
25
26 <main>
27
28 <h2>Today's Healthy Ten</h2>
29
30 <section id="dish-list"></section>
31
32 <h2>Order Here</h2>
33
34 <form novalidate>
35 <p>
36 <label for="cust-name">Your name</label><br>
37 <input type="text" id="cust-name" name="cust-name">
38 <span class="error" id="err-name"></span>
39 </p>
40 <p>
41 <label for="cust-phone">Phone number</label><br>
42 <input type="tel" id="cust-phone" name="cust-phone">
43 <span class="error" id="err-phone"></span>
44 </p>
45 <fieldset>
46 <legend>Pick your dish</legend>
47 <label><input type="radio" name="dish" value="jonna-rotte-wrap"> Jonna Rotte Wrap</label><br>
48 <label><input type="radio" name="dish" value="sajja-roti-wrap"> Sajja Roti Wrap</label><br>
49 <label><input type="radio" name="dish" value="ragi-sangati-bowl"> Ragi Sangati Bowl</label><br>
50 <label><input type="radio" name="dish" value="ragi-idli-bowl"> Ragi Idli Bowl</label><br>
51 <label><input type="radio" name="dish" value="pesarattu-with-sprouts"> Pesarattu with Sprouts</label><br>
52 <label><input type="radio" name="dish" value="ulava-charu-protein-bowl"> Ulava Charu Protein Bowl</label><br>
53 <label><input type="radio" name="dish" value="gongura-sprouts-salad"> Gongura Sprouts Salad</label><br>
54 <label><input type="radio" name="dish" value="sprouts-moong-chilla"> Sprouts Moong Chilla</label><br>
55 <label><input type="radio" name="dish" value="paneer-protein-bowl"> Paneer Protein Bowl</label><br>
56 <label><input type="radio" name="dish" value="millet-protein-shake"> Millet Protein Shake</label>
57 </fieldset>
58 <span class="error" id="err-dish"></span>
59 <p>
60 <label for="qty">Quantity</label><br>
61 <input type="number" id="qty" name="qty" min="1" max="10" value="1">
62 <span class="error" id="err-qty"></span>
63 </p>
64 <p>
65 <label for="notes">Notes for the kitchen</label><br>
66 <textarea id="notes" name="notes" rows="3" cols="40"></textarea>
67 </p>
68 <p>
69 <button type="submit">Place order</button>
70 </p>
71 </form>
72 <p id="order-result"></p>
73
74 </main>
75
76 <footer>
77 <p>&copy; 2026 Poshtik Campus &middot; orders@poshtikcampus.in</p>
78 </footer>
79
80 <!-- script.js builds the dish cards and checks every order. It is the LAST
81 line before </body>, so #dish-list and the form already exist. -->
82 <script src="script.js"></script>
83
84</body>
85</html>
WHERE EXACTLY · style.css
FILEposhtik-campus\style.css
FINDline 65, the one-line button rule body. Line 66 is the @media comment.
INSERTend of line 65, Enter, type lines 66 to 78
NEVERbelow the @media block; it stays last in the file
CHECK94 lines; line 79 is the @media comment
style.css · lines 64–81ONE RULE PER PRESS
64button
65{ background-color: seagreen; color: white; border: none; padding: 8px 16px; }
66/* ===== Lab 5 — the order form talks back ===== */
67.error
68{
69 display: block; color: red; font-size: 14px;
70}
71#order-result
72{
73 background-color: mintcream; border: 2px solid seagreen; padding: 10px; max-width: 480px;
74}
75#order-result:empty
76{
77 display: none; /* hidden until there is a message to show */
78}
79/* ===== phone layout — the media query MUST stay last ============ */
80@media (max-width: 600px)
81{
WHERE EXACTLY · script.js · THE CHECKS
FILEposhtik-campus\script.js
FINDline 58, /* ===== MPS-02 · read the order form on click =====, down to line 107, the last } in the file
DELETElines 58 to 107
INSERTat line 58, type lines 58 to 110 from the panel
NEVERtouch lines 1 to 57; Exercise 1 lives there
CHECK110 lines; reload and the Console still starts with dishes rendered: 10
script.js · lines 58–110ONE LINE PER PRESS
58/* ===== EXERCISE 2 · validate the order on submit ===== */
59const orderForm = document.querySelector("form");
60const orderResult = document.getElementById("order-result");
61
62// Writes a message into one error span. An empty message clears it.
63function showError(id, message) {
64 document.getElementById(id).textContent = message;
65}
66
67orderForm.addEventListener("submit", function (event) {
68 event.preventDefault(); // stop the browser from reloading the page
69
70 // Wipe the messages left over from the last attempt.
71 ["err-name", "err-phone", "err-dish", "err-qty"].forEach(function (id) {
72 showError(id, "");
73 });
74 orderResult.textContent = "";
75
76 // Read the fields. .value is always a string, so trim it or convert it.
77 const customerName = document.getElementById("cust-name").value.trim();
78 const phone = document.getElementById("cust-phone").value.trim();
79 const picked = document.querySelector("input[name='dish']:checked");
80 const quantity = Number(document.getElementById("qty").value);
81 let problems = 0;
82
83 if (customerName === "") {
84 showError("err-name", "Please enter your name.");
85 problems = problems + 1;
86 }
87 if (!/^[0-9]{10}$/.test(phone)) { // exactly 10 digits, nothing else
88 showError("err-phone", "Phone number must be exactly 10 digits.");
89 problems = problems + 1;
90 }
91 if (picked === null) { // no radio in the group is checked
92 showError("err-dish", "Please pick a dish.");
93 problems = problems + 1;
94 }
95 if (!Number.isInteger(quantity) || quantity < 1 || quantity > 10) {
96 showError("err-qty", "Quantity must be a whole number from 1 to 10.");
97 problems = problems + 1;
98 }
99
100 if (problems > 0) {
101 console.log("order refused: " + problems + " problem(s)");
102 return; // stop here: a bad order is never placed
103 }
104
105 // All checks passed. The label wrapped around the radio holds the dish name.
106 const dishName = picked.parentElement.textContent.trim();
107 const order = { customerName: customerName, phone: phone, dish: dishName, quantity: quantity };
108 orderResult.textContent = "Order placed: " + quantity + " × " + dishName + " for " + customerName;
109 console.log(order);
110});
REAL FILES · ALL SIX RUNNING · CONSOLE IS THE FRAME'S OWN
file:///C:/Users/student/Desktop/poshtik-campus/menu.html
CONSOLE · F12 PRINTED BY THE FRAME'S SCRIPT.JS
Waiting for the frame to load script.js…
Run tests A, B and C from Part 8 in the frame. Scroll to Order Here. Each refusal adds an order refused line; test C adds the order object. Nothing reloads. Switch to index.html and the Console stays quiet: that page loads no script.

PART 10 · DEBRIEF · 9 MIN

Common mistakes, one commit, what's next.

WHAT YOU SEEWHYFIX
Console says dishes rendered: 10 but no cards showThe card was built but never added to the pagedishList.appendChild(card); as the last line inside the loop
Cannot read properties of null (reading 'appendChild')The id in script.js does not match menu.html, or the script tag sits above the sectionSame spelling, dish-list; script tag last before </body>
The page reloads and the red messages flash awaypreventDefault() is missing, or the listener is on the button's click instead of the form's submitorderForm.addEventListener("submit", …) with event.preventDefault() first
A name of only spaces is acceptedNo .trim().value.trim() before comparing with ""
Quantity 2.5 is accepted, or a valid 3 is refusedNo whole-number check, or Number.isInteger ran on the stringNumber(…) first, then Number.isInteger and the range
Old red messages stay after you fix a fieldMessages are not cleared at the start of each submitEmpty the four spans and #order-result before the checks
A grey browser bubble appears for quantity 11 and your message never showsThe browser checks max="10" itself and blocks the submitAdd novalidate to <form>

Say these out loud

I can build page elements from an array with forEach, createElement and appendChild.
I can stop a form's reload with preventDefault and run my own checks on submit.
I can clean input first: trim for text, Number for numbers, a pattern for a phone.
I can show every problem beside its field, clear old messages, and place a good order.

Commit and push

1
Test your own site

Run tests A, B and C from Part 8 on your menu.html. All three must match.

2
Stage the three filesgit add menu.html script.js style.css
3
Commitgit commit -m "Render menu from data and validate orders"
4
Push, then lookgit push

Refresh your repo on GitHub and find the new commit on top.

YOUR FOLDER AFTER LAB 5
poshtik-campus/
index.html
menu.html 85 lines · #dish-list · 4 error spans · #order-result
about.html
style.css 94 lines · 13 new lines above @media
script.js 110 lines · cards from data + order checks
images/ unchanged
Next: Unit 3.

Unit 3 starts a React version of this same Poshtik Campus site.