Class 16 — JavaScript finally touches the page
So far your code only spoke to the console. Today it reaches into the page — and changes what the visitor actually sees.
Every console.log you have written so far talks to a hidden developer panel — the visitor never sees it. But a real website changes on screen: a price updates, a heading turns green, an error message appears. To do that, JavaScript needs a way to grab a piece of the page and edit it. That way is the DOM — the Document Object Model — the browser's live, editable model of your HTML. Today you'll learn to select an element, read and change its text, and restyle it from JavaScript — with every change happening for real, in front of you.
Walk out of this room able to…
Every DOM demo runs live, right on this slide. To practise on your own machine, make a fresh throwaway folder fswd-practice\Class-16\, open it in VS Code, and create a scratch pair — one index.html and one script.js beside it — build tiny pages from scratch, run them, throw them away. Nothing here is committed: fswd-practice\ is scratch paper, never a git repo. The real poshtik-campus\ project — and its commits — live only in the lab classes. Never here.
To see the DOM on your own machine: open your .html file in the browser, then press F12 (or right-click → Inspect). The Elements tab shows the live DOM tree; the Console tab is where every console.log and every red error appears. You'll live in both tabs today.
The one idea everything else stands on
The DOM is your HTML, drawn as a living tree the browser keeps in memory.
When the browser reads your HTML file, it doesn't just paint it once and forget it. It builds a tree in memory — one node for every tag — and keeps that tree alive the whole time the page is open. That tree is the DOM. Your HTML file is the blueprint (written once, on disk); the DOM is the live building (standing in memory, editable). JavaScript never edits your .html file — it edits the live tree, and the screen instantly redraws to match.
Say it in one line: "The DOM is the browser's live, in-memory tree of my HTML — one node per tag — and JavaScript changes the page by changing that tree." The words "live" and "tree" are the two the examiner is listening for. The file on disk never changes; the tree in memory does.
You just spent Class 15 on objects — bundles of labelled values you read with a dot. The DOM is exactly that idea applied to the page: every node is an object with properties (.textContent, .style, .id) you read and set with a dot, just like dish.price. That is literally why it's called the Document Object Model: your whole page is one big tree of objects. Everything you learned yesterday transfers directly today.
Before any JavaScript: build the page it will reach into — from scratch
A DOM lesson with no page is a lesson about reaching into thin air. So we build the page first, by hand, one line per press — nothing downloaded, nothing pre-made. This is the exact tree you just saw drawn above, now written as a real file. Read the three ids and the one repeated class carefully — site-name, tagline, price, and class="dish". Every single line of JavaScript for the rest of today reaches for one of those four names. Nothing else in this class is new HTML; this one file carries the whole hour.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Poshtik Campus</title> <!-- document.title reads THIS --> <link rel="stylesheet" href="style.css"></head><body> <h1 id="site-name">Poshtik Campus</h1> <p id="tagline">Healthy food, made on campus.</p> <p>Today's special: <span id="price">Ragi Idli — ₹30</span></p> <p class="dish">Ragi Idli</p> <p class="dish">Millet Pongal</p> <p class="dish">Sprout Salad</p> <script src="script.js"></script> <!-- LAST line in body. Part 11 explains why --></body></html>19 lines, no CSS framework, no library. THREE ids + ONE repeated class — that is the entire surface today's JavaScript grabs.Healthy food, made on campus.
Today's special: Ragi Idli — ₹30
Ragi Idli
Millet Pongal
Sprout Salad
↑ PLAIN HTML ONLY — style.css IS STILL AN EMPTY FILE, SO NO COLOURS AND NO RULES YET
style.css is linked on line 6 but still empty, so you are seeing raw browser defaults. The next panel fills that sheet in, one rule at a time. script.js is wired last in <body> (Part 11 explains why).Why the three ids are not decoration. An id is a handle — the name JavaScript uses to grab one exact element. Look at line 9: because that <h1> carries id="site-name", Part 4 can write document.getElementById("site-name") and get that heading and no other. No id, no handle, no reach. And the repeated class="dish" on lines 13–15 is the opposite tool: a label shared by many elements, which is exactly what querySelectorAll(".dish") in Part 5 collects in one line.
The stylesheet, also from scratch — because Part 8 is going to fight with it
Line 6 of the page linked a style.css, so let's write it — kept deliberately tiny, only what the demos need to be visible. Pay attention to lines 11–15: they give #price a plain grey, bold look. Remember that grey. In Part 8 JavaScript will set .style.color on that same element and win against this rule — and you'll see exactly why.
/* style.css — only what today's demos need to be visible */body{ font-family: Georgia, serif; padding: 20px;}h1{ font-size: 26px;}#price /* Part 8's JavaScript will override this */{ color: #333333; font-weight: bold;}.dish{ border-bottom: 1px solid #cccccc;}19 lines of CSS, four rules. Selector shapes you already own from Unit 1: tag (body, h1), id (#price), class (.dish).Healthy food, made on campus.
Today's special: Ragi Idli — ₹30
Ragi Idli
Millet Pongal
Sprout Salad
↑ PLAIN HTML ONLY — style.css IS STILL EMPTYWatch the same page change four times: the whole page switches to Georgia and gains breathing room, the heading grows, the price turns grey and bold, then each dish gets its hairline rule.
#price is the exact rule Part 8 defeats.Notice how little markup a serious JavaScript lesson requires: 19 lines of HTML and 19 of CSS, all of it built in front of you from Unit 1 skills you already have. That is deliberate. From here on, every new thing you see is JavaScript — if a demo mentions an element, it is one of the four names on this page (site-name, tagline, price, .dish). Keep this file's picture in your head and no selector today can surprise you.
Your one door into the page
The document object — the single entry point to the whole DOM tree.
You don't reach into the tree by magic. The browser hands you one ready-made object called document — it is the page. From document you can reach anything: its title (document.title), its body (document.body), and — most importantly — the methods that find elements inside it. Everything you do today starts with the word document.
Build the page from scratch, then read and change its own title
A .js file cannot be double-clicked into life — before any DOM code exists there must be a page for it to reach into. So the same fixed order as always: plain HTML first, then CSS one rule per press, then the JavaScript. Watch the right: the page appears with browser-default looks, each CSS rule lands, then the script runs and the tab label changes while the <h1> stays put.
<!-- FILE 1 of 3 · title.html — the page whose title we read and set --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Poshtik Campus</title> <!-- what document.title reads --></head><body> <h1>Poshtik Campus</h1> <p class="tip">Watch the tab label, not this page.</p></body></html>/* HTML complete & PLAIN above. FILE 2 of 3 · the CSS — one rule per press ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } h1 { color: #7C3AED; font-size: 20px; } .tip { color: #64748B; font-size: 13px; } </style><!-- CSS done. Hire the passenger — last line before </body> --> <script src="script.js"></script>// ————— FILE 3 of 3 · script.js — the smallest possible DOM edit —————// document IS the page — start every DOM job hereconsole.log(document.title); // read the tab text// setting it changes the browser tab, livedocument.title = "Menu · Poshtik Campus";console.log(document.title); // read again — it really changeda property you can READ (line 2) and SET (line 4). The <h1> never moved — only the TAB changed, because document.title is the <title> in the head, not the body.Poshtik Campus
Watch the tab label, not this page.
PLAIN HTML — NO CSS AND NO SCRIPT YET<h1> stayed put. document.title edits the <title> in the head, not the body.Now run those two lines for real, yourself
The panel above showed the sequence. This device is live: press each button and the fake tab's label really updates, driven by the same one-line code shown on the button.
document IS GLOBAL — YOU NEVER DECLARE ITYou never write let document = …. The browser creates it for you the moment the page loads and puts it in the global scope, so it's just there, everywhere in your script. Same with window (Part 9). These two are the only "free" objects you get without asking — everything else you build yourself.
The most-used line in front-end code
document.getElementById() — grab exactly one element by its id.
To change an element, you first have to hold it. The oldest and clearest way is by its id — the unique label you give a single element in the HTML. document.getElementById("price") hunts through the tree and hands you back that one element as an object, which you usually store in a variable so you can edit it on the next line. An id is unique, so this always returns one element (or null if nothing matches — remember that for Part 8).
The three lines of HTML this whole Part depends on
Every id JavaScript grabs had to be written in HTML first. So before the code, here is the markup — three plain lines, three ids, nothing else. Step it and watch the plain page appear; then one CSS line gives #price its grey so you can later catch JavaScript overruling it.
<body> <h1 id="site-name">Poshtik Campus</h1> <p id="tagline">Healthy food, made on campus.</p> <p>Today's special: <span id="price">Ragi Idli — ₹30</span></p></body>/* Three ids now exist in the tree. Add the one style rule ↓ */ #price { color: #333333; font-weight: bold; }No id in the HTML = nothing for getElementById to find. These three names are the ONLY handles the buttons below can reach.Healthy food, made on campus.
Today's special: Ragi Idli — ₹30
↑ PLAIN HTML — ALL DEFAULT BLACK, NO CSS YETThree plain lines grow first. Then the single rule lands and only the price turns grey and bold — the exact look Part 8's JavaScript will overrule.
site-name, tagline, price — now JavaScript has something to grab.Now pick an id — watch getElementById reach into that live page and grab it
The mini-page on the right is the markup you just wrote. Step the code, then click an id button: JavaScript really runs document.getElementById(...) and the element it grabs lights up in the page. That highlight is the returned element.
// the HTML has: <h1 id="site-name">…</h1>const heading = document.getElementById("site-name");console.log(heading); // the <h1> element itselfone id → one element. Click the id buttons on the right to run this for real against each id.Poshtik Campus
Healthy food, made on campus.
Today's special: Ragi Idli — ₹30
Try the last button — "discount". There is no element with that id, so getElementById returns null, nothing lights up, and the strip shows null. Remember this: a wrong or missing id gives you null, not an error — the error comes one line later, when you try to edit null. That exact trap is Part 8's debugging drill.
Select the way you already think — in CSS
querySelector & querySelectorAll — grab elements using CSS selectors.
You already know how to point at elements — you did it all through Unit 1 with CSS selectors like .dish, #price, h1. These two methods let JavaScript use that exact same language. document.querySelector(".dish") returns the first match; document.querySelectorAll(".dish") returns every match as a list you can loop over. On a real menu page with 15+ dish cards, that second one hands you all fifteen at once.
First — the markup the selector is aiming at (plain HTML, then two CSS rules)
You cannot select what you cannot see. Before any JavaScript, here is the page those selectors will hit: the same class="dish" from Part 2, just repeated fifteen times (a real menu, like Class 4's). Step it: first the plain HTML rows appear with no styling at all, then the two CSS rules turn them into cards. Only then does the selector question make sense.
<body> <div class="dish">Ragi Idli — ₹30</div> <div class="dish">Pesarattu — ₹45</div> <div class="dish">Millet Khichdi — ₹90</div> <!-- ...twelve more .dish rows, fifteen in total --></body>/* HTML is complete & plain above — fifteen bare rows. Now the sheet ↓ */ .dish { display: inline-block; margin: 4px; } /* sit side by side */ .dish { border: 1.5px solid #CBD5E1; padding: 6px 10px; } /* card look */Fifteen elements, ONE shared class name. That single repeated label is what makes the next panel's one-liner reach all fifteen.Same fifteen rows throughout — only the sheet changes. Rule one lets them share a line; rule two draws the border and padding that make them look like cards.
class="dish". Next panel points JavaScript at exactly this markup.Now run a selector against that very menu — feel the difference at scale
The page on the right is the markup you just built, at full size. Step the code, then click a selector: querySelector highlights just the first match; querySelectorAll highlights every match and shows the real count. This is why the "All" version matters — one line reaches fifteen elements.
// first match only — one element (or null)const first = document.querySelector(".dish");// EVERY match — a list you can count and loopconst all = document.querySelectorAll(".dish");console.log(all.length); // how many dishes?querySelector = the first. querySelectorAll = all of them (a NodeList). Click the buttons on the right.Rule of thumb: if you're grabbing one known, unique thing, getElementById("price") is clearest (and fastest). If you're thinking in CSS, or grabbing many things, use querySelector / querySelectorAll. Note the tiny gotcha: querySelector("#price") needs the # (it's a CSS selector), but getElementById("price") does not (it wants the bare id). Mixing those up is a classic first-week slip.
Watch the Elements panel update the instant JavaScript changes the DOM.
Here is the single most convincing proof that "the DOM is live, the file is not." On the left is your HTML source — the blueprint on disk, which never changes. On the right is the browser's Elements panel — the live DOM tree. Press Run the script and watch: the source stays frozen, but the Elements panel's text node changes before your eyes. Predict first: which side changes, and which stays the same?
<h1 id="site-name">Poshtik Campus</h1>document.getElementById("site-name").textContent = "Poshtik Campus — Open!";Commit to your prediction first.
The verdict. The source file on disk did not change — reload from disk and you'd get "Poshtik Campus" back. Only the live DOM changed, which is why only the Elements panel updated and the screen redrew. This is the whole mental model of today in one drill: JS edits the live tree, never the file.
Change what the visitor reads
.textContent — read an element's words, or replace them entirely.
Once you're holding an element, .textContent is its words. Read it (heading.textContent) to find out what it says; assign to it (heading.textContent = "…") to replace what it says — and the page updates instantly. It's the DOM twin of the object property you learned yesterday: same dot, same read/write, but now it changes the screen.
Build the page from scratch, then read and replace its heading text
The id="site-name" that JavaScript grabs must exist in HTML first, and the grey it overrules must come from CSS first. So the usual order: plain HTML → CSS → JS. Watch the right-hand page appear with browser defaults, take each CSS rule, then have its heading words replaced by the script.
<!-- FILE 1 of 3 · text.html — the heading whose words we will replace --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>textContent</title></head><body> <h1 id="site-name">Poshtik Campus</h1> <p class="tip">One real DOM element, above.</p></body></html>/* HTML complete & PLAIN above. FILE 2 of 3 · the CSS — one rule per press ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } #site-name { color: #0E7490; font-size: 21px; } .tip { color: #64748B; font-size: 13px; } </style><!-- CSS done. Hire the passenger — last line before </body> --> <script src="script.js"></script>// ————— FILE 3 of 3 · script.js — read the words, then replace them —————const heading = document.getElementById("site-name");console.log(heading.textContent); // read: "Poshtik Campus"heading.textContent = "Poshtik Campus — Now Open!";read it (line 2), or set it (line 3). Notice the CSS colour SURVIVED the text change — you replaced the words, not the styling.Poshtik CampusPoshtik Campus — Now Open!
One real DOM element, above.
PLAIN HTML — NO CSS AND NO SCRIPT YET<h1> now reads different words — but it kept the teal from #site-name. .textContent swaps the words; the CSS stays exactly where it was.Now type new words yourself and watch the real heading change
This device is live: every keystroke runs heading.textContent = yourText for real, and the heading below updates instantly. This is exactly how a "live preview" field works on real sites.
Poshtik Campus
The heading above is one real DOM element. You are editing its textContent as you type.
.textContent vs .innerHTML.textContent treats what you give it as plain text — safe, and what you want 95% of the time. There's a sibling, .innerHTML, that treats the string as HTML (so "<b>hi</b>" would render bold). It's powerful but risky — feeding user text into .innerHTML is a classic security hole. For changing words, always prefer .textContent.
Restyle the page from JavaScript
element.style.property — change an element's CSS from code.
JavaScript can also change how an element looks. Hold the element, then set element.style.color, element.style.background, element.style.fontSize — each one maps to a CSS property you already know. One tiny naming rule: CSS font-size becomes fontSize in JS (drop the dash, camelCase it), because a dash isn't allowed in a property name. This is how a button turns a total red when a form is invalid, or green when it's saved.
First remember what the CSS already said
This is the moment Part 2's stylesheet earns its place. Our style.css already gave #price a rule — go back and look at lines 11–15 if you need to. So the element arrives on screen grey and bold, decided by CSS. Now JavaScript is about to set .style.color on that same element. Two instructions, one property. Who wins?
#price { color: #333333; } — style.css, line 13 (from Part 2)tag.style.color = "seagreen"; — script.js, running after the page loadedThe answer, and it's pure Unit 1 cascade. Green wins. When you write element.style.color in JavaScript, the browser does not edit your stylesheet — it writes an inline style straight onto that one element, exactly as if the HTML had said <span id="price" style="color:seagreen">. And you learned in Unit 1 that inline styles sit at the top of the cascade, above id rules, above class rules, above everything in the sheet. So JS-set styles always beat style.css. Useful to know, and also a warning: this is why heavy .style use gets messy fast — you're scattering inline styles that later CSS can never override.
Build it from scratch — watch CSS make it grey, then JavaScript overrule
This is the cascade question made visible, so we build all three files in order: plain HTML, then the CSS that makes the price grey and bold, then the JavaScript that overrules it. Keep your eye on the price tag in the preview: it starts plain black, turns grey when the CSS rule lands, then turns green the moment JS writes an inline style.
<!-- FILE 1 of 3 · style-demo.html — the price tag JS will restyle --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>JS .style vs CSS</title></head><body> <h1>Poshtik Campus</h1> <p>Today's special: <span id="price">Ragi Idli — ₹30</span></p></body></html>/* HTML complete & PLAIN above. FILE 2 of 3 · the CSS — this is what JS will fight ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } h1 { font-size: 20px; color: #0F172A; } #price { color: #333333; font-weight: bold; } </style><!-- CSS done — the price is GREY now. Hire the passenger --> <script src="script.js"></script>// ————— FILE 3 of 3 · script.js — one .style property at a time —————const tag = document.getElementById("price");tag.style.color = "seagreen"; // beats #price in style.csstag.style.fontSize = "22px"; // note: fontSize, not font-sizetag.style.background = "mintcream"; // a highlightCSS font-size → JS fontSize (camelCase). And notice: green won, because .style writes an INLINE style — top of the cascade.Poshtik Campus
Today's special: Ragi Idli — ₹30
PLAIN HTML — NO CSS AND NO SCRIPT YET ✓ INLINE STYLE FROM JS BEAT #price IN style.css#price) → green (JS .style.color). JavaScript didn't edit your stylesheet — it stamped an inline style on that one element, which sits at the top of the cascade.Now recolour it yourself — one real property at a time
This device is live: each button runs one real .style assignment on the price tag below, and you see it recolour, grow, or get a background instantly. Pick a swatch to set the colour to any value.
Poshtik Campus
Today's special: Ragi Idli — ₹30
The site is blank and the console is red. Find the one-character bug.
This is the error you will hit more than any other in your first month: "Cannot set properties of null (setting 'textContent')". It always means the same thing — getElementById handed you back null because the id you asked for doesn't exist (usually a typo), and then the next line tried to edit null. Read the code, spot the mismatch, and say the fix out loud before you reveal.
<h1 id="site-name">Poshtik Campus</h1>const h = document.getElementById("sitename");h.textContent = "Now Open!";h equal to null? Which line actually throws — the getElementById line, or the next one? What's the fix?Say the fix out loud first.
First, the page itself — because the bug is a mismatch WITH the HTML
You cannot judge a wrong id without seeing the right one. So here is the whole broken site built from scratch — plain HTML → CSS → JS. Read line 8 carefully: the id is site-name, with a dash. Then watch the script fail on the right.
<!-- FILE 1 of 3 · broken-site.html — note the EXACT id on line 8 --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Poshtik Campus</title></head><body> <h1 id="site-name">Poshtik Campus</h1> <!-- DASH! --> <p class="tip">The heading should say "Now Open!"</p></body></html>/* HTML complete & PLAIN above. FILE 2 of 3 · the CSS — one rule per press ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } #site-name { color: #B91C1C; font-size: 21px; } .tip { color: #64748B; font-size: 13px; } </style><!-- CSS done. Hire the passenger — last line before </body> --> <script src="script.js"></script>// ————— FILE 3 of 3 · script.js — the BROKEN version —————// HTML id is "site-name" (with a dash)const h = document.getElementById("sitename"); // ✗ no dash → nullh.textContent = "Now Open!"; // ✗ THIS line throwsthe heading NEVER changed — it still says "Poshtik Campus". Line 2 silently gave null; line 3 is where it crashed.Poshtik Campus
The heading should say "Now Open!"
PLAIN HTML — NO CSS AND NO SCRIPT YETNow the two scripts, side by side
// HTML id is "site-name" (with a dash)const h = document.getElementById("sitename"); // ✗ no match → nullh.textContent = "Now Open!"; // ✗ THIS line throws// HTML id is "site-name" (with a dash)const h = document.getElementById("site-name"); // ✓ found ith.textContent = "Now Open!"; // ✓ worksThe two-step truth. Line 2 does not crash — a bad id quietly returns null. The crash is on line 3, when you read .textContent of null. So the console's line number points at line 3, but the bug is on line 2. Fix rule: when you see Cannot set properties of null, go check the getElementById / querySelector just above it and make its id/selector match the HTML character for character.
One level up from the document
The window object — the whole browser tab, briefly.
If document is the page, window is the tab that holds it — the browser viewport itself. You'll meet it fully later; today just one useful property: window.innerWidth, the width of the viewport in pixels. It connects straight back to Unit 1's media queries — a media query reacts to width in CSS; window.innerWidth reads that same width in JavaScript.
Build the page from scratch — the same width, once in CSS and once in JS
This one has a bonus: the CSS file contains a media query, so you see the same viewport width used twice — first by CSS, then by JavaScript. Usual order: plain HTML → CSS → JS.
<!-- FILE 1 of 3 · viewport.html — the page that reads its own width --><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>window.innerWidth</title></head><body> <h1>How wide is this tab?</h1> <p class="tip">CSS reacts to the width; JS can read it.</p></body></html>/* HTML complete & PLAIN above. FILE 2 of 3 · the CSS — one rule per press ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } h1 { color: #B45309; font-size: 20px; } .tip { color: #64748B; font-size: 13px; } </style><!-- CSS done. Hire the passenger — last line before </body> --> <script src="script.js"></script>// ————— FILE 3 of 3 · script.js — reading the viewport width —————// window = the browser tab itselfconsole.log(window.innerWidth); // viewport width in pxthe same width a CSS media query watches — now readable as a NUMBER from JS. Press the button below to read it live.How wide is this tab?
CSS reacts to the width; JS can read it.
PLAIN HTML — NO CSS AND NO SCRIPT YET1280, not "1280px" — a plain number you can do maths with. That's the difference between CSS reacting to width and JS reading it.Now read YOUR browser's real width, live
Press the button: it runs window.innerWidth for real and prints this browser's actual current width. Resize your window and press again — the number changes, because it genuinely reads the live viewport.
alert(), confirm()| Call | What it does | Returns |
|---|---|---|
| alert("Saved!") | pops a message box the user must dismiss | nothing (undefined) |
| confirm("Delete?") | pops an OK / Cancel box | true (OK) or false (Cancel) |
| window.innerWidth | current viewport width in pixels | a number, e.g. 1280 |
| window.innerHeight | current viewport height in pixels | a number, e.g. 720 |
These are window methods, so you can write window.alert(...) or just alert(...) — window is the default object, so it's optional. Skim this now; you'll use confirm() for a real "are you sure?" in a later lab.
The script runs too early — and reads an element that isn't there yet.
A subtler cousin of Part 9's bug, and the reason so many first scripts "just don't work." The id is spelled perfectly — but the <script> sits in the <head>, so it runs before the browser has built the <body>. At that moment the element genuinely doesn't exist yet, so getElementById returns null again. Diagnose the timing, not the spelling.
Here is the whole broken file, from scratch — because with a timing bug where the tag sits IS the bug, and you can only see that in the complete file. Read it top to bottom the way the browser does, and mark the exact instant the script runs.
<!DOCTYPE html><html lang="en"><head> <title>Poshtik Campus</title> <script> <!-- ✗ the bug: this runs NOW, in the head --> const t = document.getElementById("tagline"); // spelling is CORRECT t.textContent = "Hi"; // ✗ throws here — t is null </script></head><body> <p id="tagline">Healthy food</p> <!-- born too LATE --></body></html>the browser reads 1→13 in order. At line 6 it has not yet reached line 11, so the p genuinely does not exist. Same id, different TIME.t still null? What are the two standard fixes?Name the cause first — it's timing, not spelling.
Cause: the script in the <head> runs while the browser is still reading top-to-bottom — the <body> and its <p id="tagline"> don't exist yet, so getElementById returns null. Fix 1 (simplest, and what the labs use): move the <script> to the very bottom of the <body>, after all the elements. Fix 2: keep it where it is but add defer — <script src="script.js" defer> — which tells the browser "run this only after the whole page is built." Either way, the rule is: your DOM code must run after the DOM exists.
Here is Fix 1 as the complete file, built in the usual order — plain HTML → CSS → JS — so you can watch the page arrive plain, take its styling, and only then get its script. The only difference from the broken version is where those script lines sit. That move is the entire fix.
<!-- PLAIN HTML first — nothing but structure --><!DOCTYPE html><html lang="en"><head> <title>Poshtik Campus</title> <!-- head is now script-free --></head><body> <h1>Poshtik Campus</h1> <p id="tagline">Healthy food</p> <!-- ✓ born FIRST --></body></html>/* HTML complete & PLAIN above. Now the CSS — one rule per press ↓ */ <style> body { font-family: Georgia, serif; padding: 18px; } h1 { color: #047857; font-size: 20px; } #tagline { color: #334155; font-size: 15px; } </style><!-- CSS done. NOW the script — and note WHERE it goes --> <script> <!-- ✓ last thing in body --> const t = document.getElementById("tagline"); // finds it t.textContent = "Hi"; // ✓ works </script>nothing was added to the LOGIC — the script block simply moved from the head to the end of the body. By the time it runs, #tagline already exists.Poshtik Campus
Healthy foodHi
PLAIN HTML — NO CSS AND NO SCRIPT YETHi — the script found the element because it existed by then. Same code as the broken file; later moment.Fix 2, in one line, for the record. If you genuinely need the script in the <head> (real projects sometimes do), keep it there and add the defer attribute to an external script: <script src="script.js" defer></script>. defer means "download it now, but don't run it until the whole page is built." Note the catch: defer works on external scripts with a src — it has no effect on an inline <script> block like line 8 above. That's one more reason our template always uses an external script.js.
Why this matters for Lab 4. Every script you write from Lab 4 onward reads from the page. If you ever see null on an id you know is spelled right, this timing bug is the first suspect — check where your <script> tag sits. That's why our lab template always puts <script src="script.js"> at the bottom of <body> — exactly as you saw on line 17 of Part 2's campus.html.
Class 16 · wrap-up
You can now reach into a live page and change it. That's the whole job of front-end JavaScript.
Selecting, reading, writing text, restyling, and reading the two most common errors — that is the core loop every interactive site runs a thousand times a second. Lab 4 puts it to real use: your script will read the Poshtik Campus order form and model a dish as an object.
Take-home kit — the DOM cheat-card
Copy this into your notes. It answers every DOM question you saw today at a glance.
- The DOM: the browser's live, in-memory tree of your HTML — one node per tag. JS edits the tree, never the file.
- Select:
document.getElementById("price")(bare id, one element) ·document.querySelector(".dish")(CSS, first) ·document.querySelectorAll(".dish")(CSS, all). - Change text:
el.textContent = "…"reads/replaces an element's words. - Change style:
el.style.color = "seagreen"· remember CSSfont-size→ JSfontSize. - The #1 error:
Cannot set properties of null→ the id/selector didn't match, OR the script ran before the DOM was built. Put<script>at the bottom of<body>.
Folder state before Lab 4 — where script.js lives now
Nothing changed on disk today (every demo ran on this slide). But here is the state your real poshtik-campus\ project is in as you walk into Lab 4 — script.js already exists from earlier labs and is linked at the bottom of each page.
fswd-practice\Class-16\ scratch folder is throwaway and never committed.| Class | Topic | What script.js could do by then |
|---|---|---|
| 13 | What is JS + first script | print to the console |
| 14 | Variables & data types | hold values, know their types |
| 15 | Objects | model one dish as an object |
| 16 | The DOM (today) | select elements, change text & style |
- Define the DOM in one sentence, using the words "live" and "tree".
- Write the line that grabs the element with id
pricetwo different ways. - Change that element's words, then turn its text green — write both lines.
- You see Cannot set properties of null. Name the two things that could cause it.
- Where should a
<script>that touches the DOM be placed, and why?