Skip to content
Chapter 05 · Slide Breakdown

JavaScript in the Front End

Where JavaScript stops being a language exercise and starts changing pages. Every lab from here uses this chapter, and three of its ideas — DOM timing, event propagation and preventDefault — cause most of the bugs you will hit all semester.

58 slidesSingle deckPure write-itBook ch. 9

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

SlidesTopicWeightWhat to do with it
1–2Title; SweetAlert as a third-party output librarySkimA CDN script tag and a Swal.fire({...}) call. Nice for labs, unlikely to be examined.
3–4The DOM; the document objectMemorizedocument is the root object representing the entire HTML document, globally accessible.
5–6Nodes, NodeLists, node propertiesMemorizeA NodeList behaves like an array. The examinable footnote: forEach works on a NodeList but not on an HTMLCollection.
7–8Selection methods, old and newWrite itThe three old ones and the two query methods. querySelector returns the first match.
9–11Element node properties; tag-specific propertiesWrite itclassList, className, id, innerHTML, style, tagName, plus href/name/src/value.
12–13Changing an element’s styleWrite itYou can set .style.x directly, but the slides say it is preferable to change className or classList.
14–15innerHTML vs textContent vs DOM manipulationMemorizeThe 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–20Family relations; DOM manipulation methods; the exerciseWrite itSeven manipulation methods. Slide 20 asks you to redo the makeArticle exercise with them — do both versions and compare.
21The dataset property and data-* attributesWrite itNote the naming rule: data-user-name becomes dataset.userName.
22–23Handling events; DOM timingMemorizeYou cannot access or modify the DOM until it has loaded. This is the setup for slide 28.
24–27Event handlers, anonymous functions, NodeList arraysWrite itRegister with addEventListener(), passing a callback. Three equivalent forms on slide 26.
28–29window.load vs DOMContentLoadedMemorizeload waits for images and stylesheets too; DOMContentLoaded fires when the HTML is downloaded and parsed. Generally the one you want.
30–31The event objectWrite itAdd a parameter, conventionally e, to the callback. e.target matters for delegation.
32–35Propagation: capturing, bubbling, stopPropagationMemorizeThe most conceptually examinable block in the chapter. Learn the two phases and their directions.
36–38Event delegationWrite itOne listener on the parent instead of one per child. Note the warning: nodeName always returns upper case.
39–42Event types: mouse, keyboard, formMemorizeFive categories. Learn the seven mouse events, the two keyboard events and the six form events.
43–46The submit event; the three form event interestsWrite itMovement between elements, data changing, and final submission. e.preventDefault() is the whole trick.
47–54Validation: empty fields, multiselect, numbers, matching emailsWrite itCopy each listing out. Slide 47 repeats the rule: validate on the server for security, on the client for speed and perceived responsiveness.
55–58Regular expressions: literals, metacharacters, syntax, methodsMemorizeThe 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

selection.jsSlides 7–8[ RUN IT → ]
// ── 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

PropertyWhat it gives you
childNodesA NodeList of this node’s children.
firstChild · lastChildFirst and last child node.
nextSibling · previousSiblingThe neighbouring nodes.
parentNodeThe parent node.
nodeNameThe name of the node — always upper case for elements.
textContentThe text content, stripped of any tags.
classListA read-only list of the element’s CSS classes, with helper methods (add, remove, toggle, contains).
classNameThe raw string value of the class attribute.
idThe element’s id.
innerHTMLAll the content of the element — text and tags. Flagged on the slide as not secure.
styleA CSSStyleDeclaration whose sub-properties map to CSS properties.
tagNameThe 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.

accessing-elements.jsSlide 11[ RUN IT → ]
<!-- 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

styling.jsSlides 12–13[ RUN IT → ]
// ── 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

ApproachCostWhen to use it
innerHTMLEvery 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.
textContentCheap, and safe — it inserts text, never markup.Any time you are inserting text. Should be your default.
createElement + appendChildMore lines, but no reparse and no injection risk.Building structure, especially inside a loop.
three-ways.jsSlide 15[ RUN IT → ]
// 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

MethodWhat it does
createElementCreates an HTML element node.
createTextNodeCreates a text node.
appendChildAdds a new child node to the end of the current node.
insertAdjacentElementInserts a new child node at one of four positions relative to the current node.
insertAdjacentTextThe same, for a text node.
removeChildRemoves a child from the current node.
replaceChildReplaces a child node with a different one.
dom-manipulation.jsSlides 17–20
// 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

dataset.htmlSlide 21[ RUN IT → ]
<!-- 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.

EventFires whenUse it?
window.loadThe 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.DOMContentLoadedThe HTML document has been completely downloaded and parsed.Generally the one you want.
dom-timing.jsSlides 28–29[ RUN IT → ]
// 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');
    });
});
Diagnostic worth memorising: if a selector returns 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

handlers.jsSlides 24–26, 30–31[ RUN IT → ]
// 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.

propagationSlides 32–33[ RUN IT → ]
   <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
stop-propagation.jsSlides 34–35[ RUN IT → ]
// 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

event-delegation.jsSlides 36–38[ RUN IT → ]
// 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 elementkeydown — a key is being pressed (first)focus — an element gains focus
dblclick — double clickedkeyup — a key is released (last)blur — an element lost focus, by click or Tab
mousedown — pressed down over an elemente.key gives the key pressedchange — an input, textarea or select had its value changed
mouseup — released over an elementselect — the user selected some text
mouseover — moved (not clicked) over an elementreset — the form was reset
mouseout — moved off an elementsubmit — the form was submitted
mousemove — moved while over an element
key-event.jsSlide 41[ RUN IT → ]
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

submit-validation.jsSlides 43, 48, 52[ RUN IT → ]
// 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

multiselect.jsSlide 49[ RUN IT → ]
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

number-validation.jsSlides 50–51
// 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

matching-emails.jsSlides 53–54
// 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:

regex.jsSlides 55–58
// 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;
Which regex method? Just need yes or no → 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

Running DOM code before the DOM exists

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.

Confusing stopPropagation with preventDefault

They 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.

Calling the handler instead of passing it

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.

Case-sensitive nodeName in delegation

if (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.

Building markup in a loop with innerHTML

Setting 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.

Wrong dataset name

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-nameuserName; single-word data-userid stays userid.

Believing client-side validation is enough

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() → HTMLCollection
  • getElementsByTagName() → HTMLCollection
  • querySelector(css) → first match
  • querySelectorAll(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.userNamedata-user-name

Building

  • createElement("p")
  • createTextNode("hi")
  • appendChild(node)
  • insertAdjacentElement/Text
  • removeChild · replaceChild

Events

  • el.addEventListener("click", fn)
  • Pass fn, never fn()
  • e.target — what generated it
  • e.currentTarget — where the handler is
  • e.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 travelling
  • e.preventDefault() — stop the browser
  • Delegation: one listener on the parent + e.target

Event types

  • Mouse: click dblclick mousedown mouseup
  • mouseover mouseout mousemove
  • Keyboard: keydown then keyup
  • Form: focus blur change select reset submit

Regex

  • Metacharacters: . [ ] \ ( ) ^ $ | * ? { } +
  • [a-z] [A-Z] [0-9] [^abc]
  • ? + * · {n} {n,} {x,y}
  • ^ starts · $ ends
  • /pattern/ig
  • test 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.

  1. Select the same element five ways: by id, by class, by tag, and with both query methods.
  2. Prove the collection trap to yourself: call forEach on the result of querySelectorAll and on getElementsByTagName, and read the error.
  3. Log the same element’s innerHTML and textContent side by side and explain the difference in one sentence.
  4. Change one element’s appearance three ways: style, className and classList.toggle. Say why the third is preferred.
  5. Write the slide 15 makeArticle function with innerHTML, then as an arrow function, then as a constructor with a method.
  6. Rewrite it again with createElement and appendChild only — the slide 20 version.
  7. Put three data-* attributes on an element, including a hyphenated one, and read all three through dataset.
  8. Break your own page: put a querySelector in <head> with no wrapper, read the error, then fix it with DOMContentLoaded.
  9. 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.
  10. Add true as the third argument to one of those listeners and observe the order change.
  11. Add stopPropagation() to the innermost handler and watch the other two go quiet.
  12. Replace ten per-image listeners with one delegated listener on the parent, using e.target.nodeName.
  13. Write a submit handler that blocks submission when a field is empty, then delete preventDefault() and watch the page navigate away.
  14. Read every selected option out of a multiselect, both ways from slide 49.
  15. Write isNumeric from memory and test it with "42", "abc" and 1/0.
  16. Write regular expressions for: a Saudi mobile number, a string of exactly four digits, and a string that starts with a capital letter.
  17. 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.

Can you answer these without scrolling up?

Question 1