What this chapter is really for
One continuous deck, and it moves in a straight line: find nodes → change nodes → respond to events → validate forms. Read it in that order and each section explains the next.
The three ideas that cause every bug
1. Timing — you cannot touch the DOM before it exists (slides 23, 28).
2. Propagation — a click on a button also fires the handlers of everything
around it (slides 32–35). 3. preventDefault — without it
a form submits regardless of what your validation decided (slides 43, 48, 54).
Where it connects
The callbacks from chapter 4 become event handlers here. The forms from chapter 2 become interactive here. The regular expressions at the end reappear as Sequelize validators in chapter 7. And the "validate on the server too" warning from chapter 2 slide 65 is repeated on slide 47 for the same reason.
All 58 slides, weighted
| Slides | Topic | Weight | What to do with it |
|---|---|---|---|
| 1–2 | Title; SweetAlert as a third-party output library | Skim | A CDN script tag and a Swal.fire({...}) call. Nice for labs, unlikely to be examined. |
| 3–4 | The DOM; the document object | Memorize | document is the root object representing the entire HTML document, globally accessible. |
| 5–6 | Nodes, NodeLists, node properties | Memorize | A NodeList behaves like an array. The examinable footnote: forEach works on a NodeList but not on an HTMLCollection. |
| 7–8 | Selection methods, old and new | Write it | The three old ones and the two query methods. querySelector returns the first match. |
| 9–11 | Element node properties; tag-specific properties | Write it | classList, className, id, innerHTML, style, tagName, plus href/name/src/value. |
| 12–13 | Changing an element’s style | Write it | You can set .style.x directly, but the slides say it is preferable to change className or classList. |
| 14–15 | innerHTML vs textContent vs DOM manipulation | Memorize | The performance argument is given explicitly: every time innerHTML is set, the HTML must be parsed, a DOM constructed and inserted. Also flagged not secure. |
| 16–20 | Family relations; DOM manipulation methods; the exercise | Write it | Seven manipulation methods. Slide 20 asks you to redo the makeArticle exercise with them — do both versions and compare. |
| 21 | The dataset property and data-* attributes | Write it | Note the naming rule: data-user-name becomes dataset.userName. |
| 22–23 | Handling events; DOM timing | Memorize | You cannot access or modify the DOM until it has loaded. This is the setup for slide 28. |
| 24–27 | Event handlers, anonymous functions, NodeList arrays | Write it | Register with addEventListener(), passing a callback. Three equivalent forms on slide 26. |
| 28–29 | window.load vs DOMContentLoaded | Memorize | load waits for images and stylesheets too; DOMContentLoaded fires when the HTML is downloaded and parsed. Generally the one you want. |
| 30–31 | The event object | Write it | Add a parameter, conventionally e, to the callback. e.target matters for delegation. |
| 32–35 | Propagation: capturing, bubbling, stopPropagation | Memorize | The most conceptually examinable block in the chapter. Learn the two phases and their directions. |
| 36–38 | Event delegation | Write it | One listener on the parent instead of one per child. Note the warning: nodeName always returns upper case. |
| 39–42 | Event types: mouse, keyboard, form | Memorize | Five categories. Learn the seven mouse events, the two keyboard events and the six form events. |
| 43–46 | The submit event; the three form event interests | Write it | Movement between elements, data changing, and final submission. e.preventDefault() is the whole trick. |
| 47–54 | Validation: empty fields, multiselect, numbers, matching emails | Write it | Copy each listing out. Slide 47 repeats the rule: validate on the server for security, on the client for speed and perceived responsiveness. |
| 55–58 | Regular expressions: literals, metacharacters, syntax, methods | Memorize | The fourteen metacharacters, the quantifiers, and the five methods on slide 58. Reappears in chapter 7 as Sequelize validators. |
Finding and changing nodes
Selecting elements — five methods
// ── the three original methods ───────────────────────────────── document.getElementById("here"); // ONE element (ids are unique) document.getElementsByClassName("thumb"); // an HTMLCollection document.getElementsByTagName("li"); // an HTMLCollection // ── the newer query methods: you write CSS SELECTORS ─────────── document.querySelector("#main a"); // the FIRST element that matches document.querySelectorAll("#menu li"); // a NodeList of ALL matches // Anything you can write in a stylesheet works here: document.querySelectorAll("section > p.intro:first-child"); document.querySelectorAll('a[href$=".pdf"]'); // ── THE COLLECTION TRAP (slide 6) ────────────────────────────── // querySelectorAll → NodeList → forEach WORKS // getElementsBy* → HTMLCollection → forEach DOES NOT document.querySelectorAll("li").forEach(li => console.log(li.textContent)); // ✓ document.getElementsByTagName("li").forEach(...); // ✗ TypeError // Two ways round it: [...document.getElementsByTagName("li")].forEach(li => { }); // spread into an array for (let li of document.getElementsByTagName("li")) { } // for…of works on both
Node and element properties
| Property | What it gives you |
|---|---|
childNodes | A NodeList of this node’s children. |
firstChild · lastChild | First and last child node. |
nextSibling · previousSibling | The neighbouring nodes. |
parentNode | The parent node. |
nodeName | The name of the node — always upper case for elements. |
textContent | The text content, stripped of any tags. |
classList | A read-only list of the element’s CSS classes, with helper methods (add, remove, toggle, contains). |
className | The raw string value of the class attribute. |
id | The element’s id. |
innerHTML | All the content of the element — text and tags. Flagged on the slide as not secure. |
style | A CSSStyleDeclaration whose sub-properties map to CSS properties. |
tagName | The element’s tag name. |
Some properties exist only on certain tags: href on <a>;
name on a, input, textarea and form
only (unlike id, which every tag has); src on img,
input, iframe and script; and value on
input, textarea and submit — which is how you read what
the user typed.
<!-- given this markup --> <p id="here">hello <span>there</span></p> <ul><li>France</li><li>Spain</li><li>Thailand</li></ul> <div id="main"> <a href="somewhere.html"><img src="whatever.gif" class="thumb"></a> </div> const node = document.getElementById("here"); console.log(node.innerHTML); // hello <span>there</span> ← tags INCLUDED console.log(node.textContent); // "hello there" ← tags STRIPPED const items = document.getElementsByTagName("li"); for (let i = 0; i < items.length; i++) console.log(items[i].textContent); // France, Spain, Thailand const link = document.querySelector("#main a"); console.log(link.href); // somewhere.html const img = document.querySelector("#main img"); console.log(img.src); // whatever.gif console.log(img.className); // thumb
Changing appearance — three ways, in order of preference
// ── 1. Direct style property. Works, but sets an INLINE style. // Note the naming: CSS background-color → JS backgroundColor. const node = document.getElementById("someId"); node.style.backgroundColor = "#FFFF00"; node.style.borderWidth = "3px"; // ── 2. className — replaces the WHOLE class attribute node.className = "card active"; // ── 3. classList — PREFERRED. Surgical, and reads better. node.classList.add("active"); node.classList.remove("hidden"); node.classList.toggle("shadow"); // on if off, off if on node.classList.contains("active"); // true / false // WHY 3 beats 1: the styling stays in the stylesheet where it belongs, // so you change the element's STATE and let CSS decide what that looks like. // It is the same separation-of-concerns argument from chapter 3.
innerHTML vs textContent vs DOM methods
| Approach | Cost | When to use it |
|---|---|---|
innerHTML | Every time it is set, the HTML must be parsed, a DOM constructed, and inserted into the document. This takes time. Also not secure — user-supplied text can inject markup. | Quick prototypes, and content you generated yourself. |
textContent | Cheap, and safe — it inserts text, never markup. | Any time you are inserting text. Should be your default. |
createElement + appendChild | More lines, but no reparse and no injection risk. | Building structure, especially inside a loop. |
// The slide 15 exercise, all three ways. // makeArticle("manager", "Director", "Salah", "Abed", "[email protected]") // ── 1. innerHTML, as a declaration ───────────────────────────── function makeArticle(id, position, name, lastName, email) { document.getElementById(id).innerHTML = ` <article> <h2>Position: ${position}</h2> <p>Name: ${name}</p> <p>Last Name: ${lastName}</p> <p>Email: ${email}</p> </article>`; } // ── 2. the same thing as an ARROW function ───────────────────── const makeArticle2 = (id, position, name, lastName, email) => { document.getElementById(id).innerHTML = `<article><h2>Position: ${position}</h2><p>Name: ${name}</p>` + `<p>Last Name: ${lastName}</p><p>Email: ${email}</p></article>`; }; // ── 3. as a CONSTRUCTOR with a method (chapter 4, slide 50) ──── function Employee(position, name, lastName, email) { this.position = position; this.name = name; this.lastName = lastName; this.email = email; this.toHTML = function () { return `<article><h2>Position: ${this.position}</h2>` + `<p>Name: ${this.name}</p><p>Last Name: ${this.lastName}</p>` + `<p>Email: ${this.email}</p></article>`; }; } document.getElementById("manager").innerHTML = new Employee("Director", "Salah", "Abed", "[email protected]").toHTML();
DOM manipulation methods — the slide 20 version of the same exercise
| Method | What it does |
|---|---|
createElement | Creates an HTML element node. |
createTextNode | Creates a text node. |
appendChild | Adds a new child node to the end of the current node. |
insertAdjacentElement | Inserts a new child node at one of four positions relative to the current node. |
insertAdjacentText | The same, for a text node. |
removeChild | Removes a child from the current node. |
replaceChild | Replaces a child node with a different one. |
// The four insertAdjacent* positions: // <!-- beforebegin --> // <p> // <!-- afterbegin --> foo <!-- beforeend --> // </p> // <!-- afterend --> // The slide 20 exercise: makeArticle with DOM methods instead of innerHTML. // More lines, but no reparse and nothing can be injected. function makeArticleDOM(id, position, name, lastName, email) { const target = document.getElementById(id); const article = document.createElement("article"); const h2 = document.createElement("h2"); h2.textContent = `Position: ${position}`; // textContent, not innerHTML article.appendChild(h2); [["Name", name], ["Last Name", lastName], ["Email", email]] .forEach(([label, val]) => { const p = document.createElement("p"); p.appendChild(document.createTextNode(`${label}: ${val}`)); article.appendChild(p); }); target.replaceChildren(article); // or: target.innerHTML = ""; target.appendChild(article); }
The dataset property
<!-- Custom data-* attributes let you attach data to an element --> <div id="container" data-userid="2356" data-user-name="Salah"> <p id="display">The user ID is:</p> </div> <script> const container = document.getElementById("container"); const user_id = container.dataset.userid; // data-userid → userid const user_name = container.dataset.userName; // data-user-NAME → userName ← camelCase! container.appendChild(document.createTextNode(`User info: ${user_id}, ${user_name}`)); </script> // NAMING RULE: hyphens in the attribute become camelCase in dataset. // data-user-name → dataset.userName. Getting this wrong gives undefined // with no error, which makes it a nasty bug to find.
Event handling, timing, propagation and delegation
Timing — read this before anything else breaks
Slide 23 states it plainly: you cannot access or modify the DOM until it has been loaded. Putting your script after the markup is one way to be sure the elements exist. The robust way is to wait for an event.
| Event | Fires when | Use it? |
|---|---|---|
window.load | The entire page has loaded, including images and stylesheets. On a slow connection or an image-heavy page this can take a long time. | Only when you genuinely need images measured. |
document.DOMContentLoaded | The HTML document has been completely downloaded and parsed. | Generally the one you want. |
// With one of these, your DOM code can live ANYWHERE — even in <head> — // as long as it does not touch the DOM outside the handler. document.addEventListener('DOMContentLoaded', function () { const menu = document.querySelectorAll("#menu li"); for (let item of menu) { item.addEventListener("click", function () { item.classList.toggle('shadow'); }); } const heading = document.querySelector("h3"); heading.addEventListener('click', function () { heading.classList.toggle('shadow'); }); });
null and the element is definitely in the HTML, it is a timing problem, not a typo. Your script ran before the parser reached that element.Registering a handler
// Define a handler, then REGISTER it on an element node by passing // the callback to addEventListener(). function named() { alert("a named handler"); } document.getElementById("btn").addEventListener("click", named); // ↑ NO parentheses. // named passes the function. named() CALLS it and passes the result. // Far more common: an ANONYMOUS function. Three equivalent versions: const btn = document.getElementById("btn"); btn.addEventListener("click", function () { alert("anonymous function"); }); document.querySelector("#btn").addEventListener("click", function () { alert("a different approach, same result"); }); document.querySelector("#btn").addEventListener("click", () => { alert("arrow syntax, same result"); }); // THE EVENT OBJECT: add a parameter (conventionally e) and the browser // hands you an object describing what happened. btn.addEventListener("click", function (e) { console.log(e.type); // "click" console.log(e.target); // the element that GENERATED the event console.log(e.currentTarget); // the element the handler is ATTACHED to });
Propagation: capturing and bubbling
When an event fires on an element that has ancestors, it propagates to those ancestors in two phases.
<html> <aside id="cart"> <div class="item"> <button class="plus">+</button> ← you click HERE (the event TARGET) </div> </aside> </html> ① CAPTURING PHASE — outermost inward html → aside → div → button The browser checks each ancestor starting from the OUTERMOST (<html>) and runs any handler registered FOR THIS PHASE, until it reaches the element that triggered the event. ② BUBBLING PHASE — target outward ← THE DEFAULT button → div → aside → html The opposite: it checks the triggering element first, then works back out through every ancestor. // addEventListener's third argument picks the phase: el.addEventListener("click", handler); // bubbling (default) el.addEventListener("click", handler, true); // capturing
// THE PROBLEM (slide 34): nested elements each with their own click // behaviour. Clicking the increment button fires the button's handler — // and then the div's, and then the aside's. The cart minimises itself // every time you add an item. // THE FIX: e.stopPropagation() const btns = document.querySelectorAll(".plus"); for (let b of btns) { b.addEventListener("click", function (e) { e.stopPropagation(); // ← the event stops here incrementCount(e); }); } const items = document.querySelectorAll(".item"); for (let it of items) { it.addEventListener("click", function (e) { e.stopPropagation(); removeItemFromCart(e); }); } const aside = document.querySelector("aside#cart"); aside.addEventListener("click", function () { minimizeCart(); }); // DO NOT CONFUSE THESE TWO: // e.stopPropagation() stops the event travelling to other ELEMENTS // e.preventDefault() stops the BROWSER's default action (submitting, // following a link). They are unrelated.
Event delegation — bubbling used deliberately
// THE NAIVE WAY: one listener per element. With 200 thumbnails that is // 200 listeners — and any image added later has none at all. const images = document.querySelectorAll("#list img"); for (let img of images) { img.addEventListener("click", someHandler); } // DELEGATION: ONE listener on the parent, using bubbling. Since the user // can click on any element inside the section, the handler must work out // whether an <img> was clicked. const parent = document.querySelector("#list"); parent.addEventListener("click", function (e) { // e.target is the object that GENERATED the event. // NOTE: nodeName ALWAYS RETURNS UPPER CASE. if (e.target && e.target.nodeName === "IMG") { doSomething(e.target); } }); // Two wins: one listener instead of hundreds, and elements added to the // list LATER are handled automatically — no re-registration needed.
Event types
Five categories: mouse, keyboard, touch, form and frame events.
| Mouse (slide 40) | Keyboard (41) | Form (42) |
|---|---|---|
click — clicked on an element | keydown — a key is being pressed (first) | focus — an element gains focus |
dblclick — double clicked | keyup — a key is released (last) | blur — an element lost focus, by click or Tab |
mousedown — pressed down over an element | e.key gives the key pressed | change — an input, textarea or select had its value changed |
mouseup — released over an element | select — the user selected some text | |
mouseover — moved (not clicked) over an element | reset — the form was reset | |
mouseout — moved off an element | submit — the form was submitted | |
mousemove — moved while over an element |
document.getElementById("pagebody").addEventListener("keydown", function (e) { let keyPressed = e.key; alert("Key " + keyPressed + " was pressed"); });
Slide 40 also sets a practice task: build a board game where tiles change colour on a mouse event. The worked version is in your study material as 09-tiles-game.
Form validation and regular expressions
Slide 44 frames it: with forms in JavaScript you care about three kinds of event — movement between elements, data being changed, and the final submission. And slide 47 restates the rule from chapter 2: validation must happen on the server for security, in case JavaScript was circumvented; doing it on the client as well reduces server load and increases the perceived speed and responsiveness of the form.
preventDefault — the single most useful line in the chapter
// Slide 43: block submission when the password is empty document.querySelector("#loginForm").addEventListener("submit", function (e) { let pass = document.querySelector("#pw").value; if (pass == "") { alert("enter a password"); e.preventDefault(); // ← prevents form submission } }); // Slide 48: the same pattern, with an arrow function const form = document.querySelector("#loginForm"); form.addEventListener("submit", (e) => { const fieldValue = document.querySelector("#username").value; if (fieldValue == null || fieldValue == "") { e.preventDefault(); // stop the submission FIRST console.log("you must enter a username"); // then tell the user } }); // To submit a form FROM JavaScript — often paired with preventDefault: const formExample = document.getElementById("loginForm"); formExample.submit();
Reading a multiselect list
const multi = document.querySelector("#listbox"); // Technique 1: loop EVERY option and test .selected for (let i = 0; i < multi.options.length; i++) { if (multi.options[i].selected) { console.log(multi.options[i].textContent); } } // Technique 2: SIMPLER — selectedOptions only contains the chosen ones for (let i = 0; i < multi.selectedOptions.length; i++) { console.log(multi.selectedOptions[i].textContent); }
Number validation, and the HTML5 validity object
// No simple built-in exists. Build one from parseFloat, isNaN and isFinite. function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // You need BOTH: 1/0 is Infinity, which isNaN() considers a number. isNumeric("42"); // true isNumeric("abc"); // false — parseFloat gives NaN isNumeric(1/0); // false — Infinity is not finite // You can also read the browser's own verdict via the validity object. // Slide 50: some browsers may not support HTML5 validation, and you want // more control over how you react to bad input — so prefer JS validation. function validateTextA() { let value = ""; if (document.getElementById("textA").validity.rangeOverflow) { value = "The value must not be greater than 100"; } document.getElementById("output").innerHTML = value; } // other validity flags: valueMissing, typeMismatch, patternMismatch, // rangeUnderflow, tooLong, stepMismatch
Comparison validation — and the mistake slides 53–54 walk you through
// SLIDE 53 — first attempt. onchange fires when you leave the field, // so the user is warned early. BUT the form can still be submitted: // nothing blocks the request. function check(e) { var email1 = document.getElementById('email_addr'); var email2 = document.getElementById('email_repeat'); if (email1.value !== email2.value) { e.preventDefault(); alert("The two emails have to match"); } } // SLIDE 54 — the fix is a SECOND check that blocks the HTTP request, // wired to the submit button as well as to onchange. // <input type="email" id="email_repeat" name="email2" required onchange="check(e)"> // <input type="submit" value="Send" onclick="check(e);"> // CLEANER — one handler on the form's submit event, no inline attributes: document.querySelector("form").addEventListener("submit", function (e) { const a = document.getElementById('email_addr').value; const b = document.getElementById('email_repeat').value; if (a !== b) { e.preventDefault(); alert("The two emails have to match"); } });
Regular expressions
A regular expression is a set of special characters defining a pattern, built from two kinds of character: literals (a character you want to match in the target text) and metacharacters (a symbol that commands the parser). There are fourteen metacharacters:
// THE FOURTEEN METACHARACTERS: . [ ] \ ( ) ^ $ | * ? { } + // In JavaScript a regex is CASE SENSITIVE and lives between forward slashes. let pattern = /ala/; // matches inside: 'Salah Althobeiti' and 'Al malaz district' "Salah Althobeiti".match(/ala/); // matched text, or null /ala/.test("Salah Althobeiti"); // true / false // ── CHARACTER CLASSES (slide 57, left) ───────────────────────── // [abc] any one of a, b or c // [^abc] any character EXCEPT a, b or c // [a-z] any lower-case letter // [A-Z] any upper-case letter // [a-zA-Z] any letter // [0-9] any digit // ── QUANTIFIERS AND ANCHORS (slide 57, right) ────────────────── // a? 0 or 1 times // a+ 1 or more times // a* 0 or more times // a{n} exactly n times // a{n,} n or more times // a{x,y} at least x, at most y times // ^a STARTS with a // a$ ENDS with a // (?=ae) any string FOLLOWED BY "ae" (lookahead) // (?!ae) any string NOT followed by "ae" (negative lookahead) // ── /pattern/modifier ────────────────────────────────────────── // /[a-z][0-9]/ig i = case-insensitive, g = global (all matches) // ── THE FIVE METHODS (slide 58) ──────────────────────────────── pattern.exec(text); // 1. matched text, or null pattern.test(text); // 2. true or false text.match(pattern); // 3. matched text or null; ALL matches with /g text.search(pattern); // 4. the INDEX of the match text.replace(pattern, "newvalue"); // 5. a new string with replacements // The slide's own phone-number example, as an HTML5 pattern attribute: // <input pattern="[+]?[0-9]{10,14}"> // optional +, then 10 to 14 digits // Practical validators built the same way: const saudiMobile = /^05[0-9]{8}$/; // exactly 05 + 8 digits const simpleEmail = /^[^@\s]+@[^@\s]+\.[a-z]{2,}$/i;
test(). Need the matched text → match(). Need where it is → search(). Need to change it → replace(). For validation you almost always want test(), and you almost always want ^ and $ so the pattern must match the whole value.The eight things people get wrong
A querySelector returns null and the next line throws "cannot read property of null" — but the element is right there in the HTML. The script simply ran before the parser reached it.
Fix: Wrap the code in document.addEventListener('DOMContentLoaded', ...), or put the script at the end of <body>. Use DOMContentLoaded rather than window.load — the latter also waits for every image.
stopPropagation with preventDefaultThey sound similar and do unrelated things. Calling stopPropagation() in a submit handler will not stop the form submitting, and preventDefault() will not stop the parent’s click handler firing.
Fix: stopPropagation() stops the event reaching other elements. preventDefault() stops the browser doing its default thing — submitting, following a link, checking a box.
btn.addEventListener("click", myHandler()) runs myHandler immediately, at registration time, and then registers whatever it returned — usually undefined.
Fix: Pass the function, do not call it: addEventListener("click", myHandler). No parentheses.
forEach on the result of getElementsBy*Those methods return an HTMLCollection, not a NodeList, and slide 6 notes the difference explicitly: forEach works on a NodeList, not on an HTMLCollection.
Fix: Use querySelectorAll (which returns a NodeList), or spread the collection into an array with [...collection], or just use for…of — which works on both.
nodeName in delegationif (e.target.nodeName === "img") is silently never true. The slide warns about this in the code comment: nodeName always returns upper case.
Fix: Compare against "IMG", or use e.target.matches("img") which takes a CSS selector and is case-insensitive.
innerHTMLSetting innerHTML inside a loop re-parses the HTML and rebuilds a DOM on every iteration. Slide 14 explains exactly this cost. It also opens an injection hole if any of the values came from a user.
Fix: Build the string once and assign at the end, or use createElement + appendChild. Use textContent for anything that is only text.
data-user-name is not dataset.user-name or dataset.username — it is dataset.userName. The wrong name gives undefined silently.
Fix: Hyphens become camelCase. data-user-name → userName; single-word data-userid stays userid.
The third time this appears in the course, because it is the point that most often loses marks. Slide 47 spells out the reasoning: check on the server for security, in case JavaScript was circumvented.
Fix: Client-side validation buys you reduced server load and perceived responsiveness. Server-side validation buys you correctness. You need both, and only one of them is optional.
Chapter 5 on a single screen
Selecting
getElementById("id")getElementsByClassName()→ HTMLCollectiongetElementsByTagName()→ HTMLCollectionquerySelector(css)→ first matchquerySelectorAll(css)→ NodeList- forEach: NodeList yes, HTMLCollection no
Reading & writing
.textContent— text, tags stripped.innerHTML— text + tags, reparses, unsafe.value— what the user typed.classList.add/remove/toggle/contains.style.backgroundColor(camelCase).dataset.userName←data-user-name
Building
createElement("p")createTextNode("hi")appendChild(node)insertAdjacentElement/TextremoveChild·replaceChild
Events
el.addEventListener("click", fn)- Pass
fn, neverfn() e.target— what generated ite.currentTarget— where the handler ise.key— the key pressed
Timing
DOMContentLoaded— HTML parsed. Use this.window.load— images and CSS too- Or put the script at the end of
<body>
Propagation
- ① capturing:
html→ target - ② bubbling: target →
html(default) e.stopPropagation()— stop travellinge.preventDefault()— stop the browser- Delegation: one listener on the parent +
e.target
Event types
- Mouse:
click dblclick mousedown mouseup mouseover mouseout mousemove- Keyboard:
keydownthenkeyup - Form:
focus blur change select reset submit
Regex
- Metacharacters:
. [ ] \ ( ) ^ $ | * ? { } + [a-z] [A-Z] [0-9] [^abc]? + *·{n} {n,} {x,y}^starts ·$ends/pattern/igtest exec match search replace
Build these with the browser open
Every one of these takes under ten minutes and each corresponds to a real slide. The DevTools console is the tool for this chapter — select things, poke at them, watch what changes.
- Select the same element five ways: by id, by class, by tag, and with both query methods.
- Prove the collection trap to yourself: call
forEachon the result ofquerySelectorAlland ongetElementsByTagName, and read the error. - Log the same element’s
innerHTMLandtextContentside by side and explain the difference in one sentence. - Change one element’s appearance three ways:
style,classNameandclassList.toggle. Say why the third is preferred. - Write the slide 15
makeArticlefunction withinnerHTML, then as an arrow function, then as a constructor with a method. - Rewrite it again with
createElementandappendChildonly — the slide 20 version. - Put three
data-*attributes on an element, including a hyphenated one, and read all three throughdataset. - Break your own page: put a
querySelectorin<head>with no wrapper, read the error, then fix it withDOMContentLoaded. - Nest a button inside a div inside an aside, give each a click handler that logs its name, and click the button. Write down the order before you run it.
- Add
trueas the third argument to one of those listeners and observe the order change. - Add
stopPropagation()to the innermost handler and watch the other two go quiet. - Replace ten per-image listeners with one delegated listener on the parent, using
e.target.nodeName. - Write a submit handler that blocks submission when a field is empty, then delete
preventDefault()and watch the page navigate away. - Read every selected option out of a multiselect, both ways from slide 49.
- Write
isNumericfrom memory and test it with"42","abc"and1/0. - Write regular expressions for: a Saudi mobile number, a string of exactly four digits, and a string that starts with a capital letter.
- Test each of those three with all five regex methods and note what each returns.
The complete example set is at chapter-5/js-front-end-all-examples/, including the event propagation demo and the tiles game from the slide 40 practice task.