{"id":"l-js-4-1","courseId":"javascript-fundamentals","lang":"en","kind":"lesson","summary":"Understand the DOM as the browser’s live tree of the page, select elements with CSS selectors, change their text, classes and attributes, and render a list from data.","html":"<p>Everything Pocket has done so far happened in the console. A real user never opens the console; they see a page. The rest of this section moves Pocket onto that page, and the first step is learning how JavaScript sees a web page at all.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The DOM: the page as objects</h2>\n<p>When a browser loads HTML, it does not keep it as text. It builds a tree of objects, one for every element, and draws the page from that tree. The tree is the <strong>DOM</strong> (Document Object Model), and the global <code dir=\"ltr\">document</code> object is its root.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">index.html</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Pocket&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">h1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"total\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Total: $0.00&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">p</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ul</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> id</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"expense-list\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">ul</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  &#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">script</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> src</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"app.js\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> defer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">script</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">body</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 640 230\" role=\"img\" aria-labelledby=\"l-js-4-1-t1\"><defs><marker id=\"arrow-l-js-4-1-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-js-4-1-t1\">The DOM tree for the Pocket page: document contains body, and body contains an h1, a p with id total, and a ul with id expense-list. JavaScript reads and changes these objects, and the browser redraws the page.</title>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"14\" width=\"140\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"39\" text-anchor=\"middle\">document</text>\n  <rect class=\"d-box\" x=\"260\" y=\"80\" width=\"120\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"105\" text-anchor=\"middle\">body</text>\n  <rect class=\"d-box\" x=\"40\" y=\"160\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"110\" y=\"187\" text-anchor=\"middle\">h1</text>\n  <rect class=\"d-box-primary\" x=\"250\" y=\"160\" width=\"140\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"320\" y=\"187\" text-anchor=\"middle\">p#total</text>\n  <rect class=\"d-box-primary\" x=\"460\" y=\"160\" width=\"160\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"540\" y=\"187\" text-anchor=\"middle\">ul#expense-list</text>\n  <path class=\"d-line\" d=\"M320 54 L320 80\"/>\n  <path class=\"d-line\" d=\"M300 120 L110 160\"/>\n  <path class=\"d-line\" d=\"M320 120 L320 160\"/>\n  <path class=\"d-line\" d=\"M340 120 L540 160\"/>\n  <text class=\"d-label-muted\" x=\"540\" y=\"130\" text-anchor=\"middle\">JavaScript changes these</text>\n</svg><figcaption>The browser turns HTML into a tree of element objects</figcaption></figure>\n<p>The DOM is live. Change an object in the tree and the browser redraws that part of the page straight away. That is all “updating the page” means.</p>\n<p>The <code dir=\"ltr\">defer</code> attribute tells the browser to run <code dir=\"ltr\">app.js</code> after it has built the whole tree. Without it, a script in the <code dir=\"ltr\">&lt;head&gt;</code> runs before the body exists and every lookup finds nothing. Scripts loaded with <code dir=\"ltr\">type=&quot;module&quot;</code>, which you will meet in section 5, are deferred automatically.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Selecting elements</h2>\n<p><code dir=\"ltr\">document.querySelector(selector)</code> takes a CSS selector, the same syntax you write in a stylesheet, and returns the first matching element. <code dir=\"ltr\">querySelectorAll</code> returns all matches.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">app.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> totalEl</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">querySelector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"#total\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// by id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> list</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">querySelector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"#expense-list\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> firstItem</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">querySelector</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"#expense-list li\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> allItems</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">querySelectorAll</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"li.big\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// every match</span></span></code></pre></div>\n<p><code dir=\"ltr\">querySelectorAll</code> returns a NodeList. It has <code dir=\"ltr\">forEach</code> and works with <code dir=\"ltr\">for...of</code>; spread it, <code dir=\"ltr\">[...allItems]</code>, when you want array methods such as <code dir=\"ltr\">map</code> and <code dir=\"ltr\">filter</code>.</p>\n<p>When nothing matches, <code dir=\"ltr\">querySelector</code> returns <code dir=\"ltr\">null</code>, and the next line that uses the result throws <code dir=\"ltr\">TypeError: Cannot set properties of null</code>. That error almost always means a typo in the selector or a script that ran too early.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Changing what is there</h2>\n<p>Once you have an element, its properties are the levers:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">app.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">totalEl.textContent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Total: $30.20\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// replace the text</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">totalEl.classList.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"over-budget\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);       </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// add a CSS class</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">totalEl.classList.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toggle</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"highlight\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// add if absent, remove if present</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">totalEl.hidden </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;                     </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// show or hide</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">list.dataset.month </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"2026-10\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;             </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// sets data-month=\"2026-10\"</span></span></code></pre></div>\n<p><code dir=\"ltr\">textContent</code> sets plain text. Prefer classes over setting <code dir=\"ltr\">style</code> directly: the CSS file decides what <code dir=\"ltr\">.over-budget</code> looks like, and JavaScript only decides <em>whether</em> the element has it. Designers can then change the look without touching your logic.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>innerHTML with user data</span></p>\n<p><code dir=\"ltr\">li.innerHTML = expense.label</code> hands the label to the HTML parser. If a user types <code dir=\"ltr\">&lt;img src=x onerror=&quot;…&quot;&gt;</code> as a label, that code runs on the page. This is called cross-site scripting (XSS), and it is one of the most common security bugs on the web. Use <code dir=\"ltr\">textContent</code> for anything that came from data or a user. Reserve <code dir=\"ltr\">innerHTML</code> for fixed markup you wrote yourself.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Creating elements and rendering a list</h2>\n<p>To add new things to the page, create elements, fill them in, and attach them:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">app.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> li</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">createElement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"li\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">li.textContent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"Coffee: $4.50\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">li.dataset.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"exp-1\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">list.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">append</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(li);</span></span></code></pre></div>\n<p><code dir=\"ltr\">createElement</code> makes an element that is not on the page yet. <code dir=\"ltr\">append</code> attaches it as the last child of <code dir=\"ltr\">list</code>, and only then does it appear. <code dir=\"ltr\">element.remove()</code> takes an element off the page.</p>\n<p>Pocket has an array of expenses and needs one <code dir=\"ltr\">&lt;li&gt;</code> per expense. The cleanest way to keep the page and the data in agreement is a <strong>render function</strong>: it clears the container, then rebuilds it from the data, every time the data changes.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">app.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> renderExpenses</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">expenses</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  list.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">replaceChildren</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// remove the old items</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expense</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> li</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> document.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">createElement</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"li\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    li.textContent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">expense</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">label</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}: ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">expense</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">amount</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    li.dataset.id </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expense.id;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (expense.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 10000</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      li.classList.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">add</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"big\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    list.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">append</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(li);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> total</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sum </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  totalEl.textContent </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `Total: ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">formatMoney</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">total</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Rebuilding everything sounds wasteful, and for thousands of items it would be. For the dozens of items in a personal app, it is fast and, more importantly, simple: there is exactly one function that decides what the list looks like, and the page can never drift out of step with the data. This idea, the screen as a function of the data, is the core of frameworks like React, which do the same thing more cleverly.</p>\n<p>The rule that makes it work is discipline about direction. Data flows one way: change the array, then call <code dir=\"ltr\">renderExpenses</code>. Never fix up a single <code dir=\"ltr\">&lt;li&gt;</code> by hand “just this once”, and never read the expenses back out of the page to calculate something; the page is a picture of the data, not the data itself. The moment two places can change what is on screen, they will eventually disagree.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Explore any page in DevTools</span></p>\n<p>Open DevTools on any website, select an element in the Elements panel, then type <code dir=\"ltr\">$0</code> in the Console. <code dir=\"ltr\">$0</code> is the element you selected, so you can try <code dir=\"ltr\">$0.textContent</code> or <code dir=\"ltr\">$0.classList</code> on real pages.</p>\n</aside>\n<p>The page now shows Pocket’s data, but it cannot react to the user yet. Next lesson: events.</p>\n","toc":[{"id":"sec-1","text":"The DOM: the page as objects","level":2},{"id":"sec-2","text":"Selecting elements","level":2},{"id":"sec-3","text":"Changing what is there","level":2},{"id":"sec-4","text":"Creating elements and rendering a list","level":2}],"takeaways":["The browser turns your HTML into the DOM, a tree of objects; changing those objects with JavaScript changes the page immediately.","<code dir=\"ltr\">querySelector</code> returns the first element matching a CSS selector, or <code dir=\"ltr\">null</code>; <code dir=\"ltr\">querySelectorAll</code> returns all matches.","Set text with <code dir=\"ltr\">textContent</code>, not <code dir=\"ltr\">innerHTML</code>, whenever the content comes from data or users, so it can never run as HTML.","Change appearance through <code dir=\"ltr\">classList</code> and let CSS decide what the classes look like.","A <code dir=\"ltr\">render</code> function that clears a container and rebuilds it from your data keeps the page and the data in sync."],"further":[{"title":"Introduction to the DOM (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction"},{"title":"Document.querySelector() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector"},{"title":"Element.classList (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/Element/classList"}],"quiz":[{"q":"<p><code dir=\"ltr\">document.querySelector(&quot;#totl&quot;).textContent = &quot;$0.00&quot;;</code> throws <code dir=\"ltr\">TypeError: Cannot set properties of null</code>. What is the most likely cause?</p>","options":[{"text":"The selector matched nothing, so <code dir=\"ltr\">querySelector</code> returned <code dir=\"ltr\">null</code>.","why":"Correct. A typo in the id (<code dir=\"ltr\">totl</code>), or running the script before the element exists, both produce <code dir=\"ltr\">null</code>."},{"text":"<code dir=\"ltr\">textContent</code> cannot be set on paragraphs.","why":"<code dir=\"ltr\">textContent</code> can be set on any element. The error says the thing before <code dir=\"ltr\">.textContent</code> was <code dir=\"ltr\">null</code>."},{"text":"The page needs to be reloaded after changing the DOM.","why":"DOM changes show up immediately. No reload is needed or wanted."}],"answer":0},{"q":"<p>A user types <code dir=\"ltr\">&lt;img src=x onerror=alert(1)&gt;</code> as an expense label. Which line displays it safely?</p>","options":[{"text":"<code dir=\"ltr\">li.innerHTML = label;</code>","why":"<code dir=\"ltr\">innerHTML</code> parses the text as HTML, so the image tag is created and its <code dir=\"ltr\">onerror</code> code runs. This is an XSS hole."},{"text":"<code dir=\"ltr\">li.textContent = label;</code>","why":"Correct. <code dir=\"ltr\">textContent</code> inserts plain text, so the user sees the characters they typed and nothing runs."},{"text":"<code dir=\"ltr\">li.innerHTML = `&lt;span&gt;${label}&lt;/span&gt;`;</code>","why":"Wrapping the label in a template literal still sends it through the HTML parser. The tag inside is still created."}],"answer":1},{"q":"<p>Your <code dir=\"ltr\">render</code> function appends one <code dir=\"ltr\">&lt;li&gt;</code> per expense. After adding a fourth expense and calling <code dir=\"ltr\">render</code> again, the list shows seven items. What is missing?</p>","options":[{"text":"Clearing the list before rebuilding it, for example with <code dir=\"ltr\">list.replaceChildren()</code>.","why":"Correct. Without clearing, every render appends a full new copy after the old items."},{"text":"A call to <code dir=\"ltr\">querySelectorAll</code> instead of <code dir=\"ltr\">querySelector</code>.","why":"The container is a single element, so <code dir=\"ltr\">querySelector</code> is right. The problem is the old children are never removed."},{"text":"<code dir=\"ltr\">const</code> instead of <code dir=\"ltr\">let</code> for the expenses array.","why":"How the array is declared does not affect the DOM. The duplicates come from never removing the previous items."}],"answer":0}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<h1>Pocket</h1>\n<p id=\"total\">Total: $0.00</p>\n<ul id=\"expense-list\"></ul>\n","css":".big { font-weight: 700; color: #b42318; }\n","js":"const expenses = [\n  { id: \"exp-1\", label: \"Coffee\", amount: 450 },\n  { id: \"exp-2\", label: \"Flight\", amount: 18900 },\n  { id: \"exp-3\", label: \"Lunch\", amount: 1350 },\n];\n\nconst formatMoney = (cents) => `$${(cents / 100).toFixed(2)}`;\n\nfunction renderExpenses(expenses) {\n  // 1. Clear #expense-list\n  // 2. Add one <li> per expense: \"Coffee: $4.50\", with data-id set to its id,\n  //    and the class \"big\" when the amount is 10000 or more\n  // 3. Set #total to \"Total: $207.00\" (the sum, formatted)\n}\n\nrenderExpenses(expenses);\n"},"solution":{"html":"<h1>Pocket</h1>\n<p id=\"total\">Total: $0.00</p>\n<ul id=\"expense-list\"></ul>\n","css":".big { font-weight: 700; color: #b42318; }\n","js":"const expenses = [\n  { id: \"exp-1\", label: \"Coffee\", amount: 450 },\n  { id: \"exp-2\", label: \"Flight\", amount: 18900 },\n  { id: \"exp-3\", label: \"Lunch\", amount: 1350 },\n];\n\nconst formatMoney = (cents) => `$${(cents / 100).toFixed(2)}`;\n\nconst list = document.querySelector(\"#expense-list\");\nconst totalEl = document.querySelector(\"#total\");\n\nfunction renderExpenses(expenses) {\n  list.replaceChildren();\n  for (const expense of expenses) {\n    const li = document.createElement(\"li\");\n    li.textContent = `${expense.label}: ${formatMoney(expense.amount)}`;\n    li.dataset.id = expense.id;\n    if (expense.amount >= 10000) {\n      li.classList.add(\"big\");\n    }\n    list.append(li);\n  }\n  const total = expenses.reduce((sum, e) => sum + e.amount, 0);\n  totalEl.textContent = `Total: ${formatMoney(total)}`;\n}\n\nrenderExpenses(expenses);\n"},"checks":[{"id":"items","test":"const items = $$('#expense-list li');\nreturn items.length === 3 && items[0].textContent.trim() === 'Coffee: $4.50' && items[2].textContent.trim() === 'Lunch: $13.50';\n"},{"id":"data-id","test":"return $$('#expense-list li').map((li) => li.dataset.id).join() === 'exp-1,exp-2,exp-3';\n"},{"id":"big-class","test":"const items = $$('#expense-list li');\nreturn items.length === 3 && items[1].classList.contains('big') && !items[0].classList.contains('big') && !items[2].classList.contains('big');\n"},{"id":"total","test":"return text('#total') === 'Total: $207.00';\n"},{"id":"rerender","test":"renderExpenses([{ id: 'x', label: '<b>Tea</b>', amount: 300 }]);\nconst items = $$('#expense-list li');\nreturn items.length === 1 && items[0].textContent.trim() === '<b>Tea</b>: $3.00' && !$('#expense-list b') && text('#total') === 'Total: $3.00';\n"}],"title":"Render Pocket's list","prompt":"<p>Write the body of <code dir=\"ltr\">renderExpenses(expenses)</code> so it rebuilds the page from the data:</p>\n<ol>\n<li>Clear <code dir=\"ltr\">#expense-list</code>.</li>\n<li>Append one <code dir=\"ltr\">&lt;li&gt;</code> per expense with the text <code dir=\"ltr\">Coffee: $4.50</code> (label, colon, formatted amount), its <code dir=\"ltr\">data-id</code> set to the expense’s <code dir=\"ltr\">id</code>, and the class <code dir=\"ltr\">big</code> when the amount is 10000 cents or more.</li>\n<li>Set <code dir=\"ltr\">#total</code> to <code dir=\"ltr\">Total: </code> followed by the formatted sum.</li>\n</ol>\n<p>Calling <code dir=\"ltr\">renderExpenses</code> again must replace the list, not add to it, and labels must be shown as plain text.</p>\n","hints":["Select the two containers once at the top: <code dir=\"ltr\">document.querySelector(&quot;#expense-list&quot;)</code> and <code dir=\"ltr\">document.querySelector(&quot;#total&quot;)</code>.","For each expense: <code dir=\"ltr\">document.createElement(&quot;li&quot;)</code>, set <code dir=\"ltr\">textContent</code>, set <code dir=\"ltr\">li.dataset.id</code>, maybe <code dir=\"ltr\">li.classList.add(&quot;big&quot;)</code>, then <code dir=\"ltr\">list.append(li)</code>.","Start the function with <code dir=\"ltr\">list.replaceChildren();</code> so a second render does not duplicate items, and compute the total with <code dir=\"ltr\">reduce</code>."],"explanation":"<p>The render function is the single place that decides what the list looks like. Clearing first with <code dir=\"ltr\">replaceChildren()</code> means calling it again, after the data changes, produces exactly one item per expense instead of appending duplicates. <code dir=\"ltr\">textContent</code> puts the label in as plain text, so a label like <code dir=\"ltr\">&lt;b&gt;Tea&lt;/b&gt;</code> is shown literally instead of becoming bold (or, with nastier input, running code). The <code dir=\"ltr\">big</code> class only flags the item; the stylesheet decides how flagged items look. <code dir=\"ltr\">dataset.id</code> writes a <code dir=\"ltr\">data-id</code> attribute you will use in the next lesson to know which expense a click belongs to.</p>\n","checkLabels":{"items":"One <li> per expense, like “Coffee: $4.50”","data-id":"Each item has its expense id in data-id","big-class":"Only expenses of 10000 cents or more get the class “big”","total":"#total shows “Total: $207.00”","rerender":"Rendering again replaces the list and shows labels as plain text"}},"words":699,"readMinutes":3}