Delegation, Submit Defaults, and a Script That Ran Too Early
A support queue page renders 12 tickets from an array into <ul id='queue'>. Each <li> contains a <button class='close'> and the developer attaches a click listener to every li at render time. Two symptoms: (a) tickets added later by an Add sample ticket button (which calls queue.appendChild(li)) cannot be closed; (b) clicking the close button on an existing ticket fires both the row handler and the close handler, producing two log lines and a confirmation dialog twice. The page also has a <form id='new-ticket'> whose submit handler is attached, but pressing Enter reloads the page and the new ticket disappears. The page is a classic script in <head> with no defer, and document.getElementById('queue') is null at the top of the file. The team has 30 minutes before a demo and wants the smallest change that fixes all three symptoms for the 12 existing rows and every row added afterwards.
1. Explain symptoms (a) and (b), then give a delegated handler that fixes both.
Read the worked answer
Listeners are attached to the specific <li> elements that existed at render time. A ticket appended later is a new node with no listener, so nothing responds - the handler exists on no ancestor either. The double firing happens because a click on the close button bubbles up to the <li>, so both the button handler and the row handler run. Delegation fixes both at once: queue.addEventListener('click', (e) => { const row = e.target.closest('li[data-id]'); if (!row) return; if (e.target.closest('.close')) { closeTicket(row.dataset.id); } else { openTicket(row.dataset.id); } });. closest also handles a click landing on an icon or span inside the button. Future rows work automatically because the event bubbles to the parent. Keep the delegation root stable; if #queue itself could be replaced, attach to document and guard with a null check.
2. Why does pressing Enter reload the page, and how should the form be wired?
Read the worked answer
A <form> submit has a default action: navigate to the URL in action (the current page if absent), which reloads and discards the in-memory ticket array. event.preventDefault() in the submit handler stops that. Pressing Enter in a text input triggers implicit submission, which is why it reproduces without clicking a button. Second trap: a <button> with no type inside a form defaults to type='submit', so a Clear button also submits - set type='button' on every non-submit button. Attach the handler to the form's submit event rather than a button's click, so Enter and click follow one path. Note that preventDefault and stopPropagation are different: the former cancels the browser action, the latter stops bubbling. Here you want preventDefault. After validating, push to the data array and update the DOM so both stay in sync.
3. Why is `getElementById('queue')` null, and what are the fixes?
Read the worked answer
The script is in <head> without defer, so it is fetched and executed during parsing, before the <ul> has been parsed. document.getElementById('queue') therefore returns null, and null.addEventListener throws a TypeError - or, if the code guards with if (queue), the listener is silently never attached. Three fixes: move the script to the end of <body>; add defer to the classic script tag, which downloads in parallel, executes after parsing in document order, and finishes before DOMContentLoaded; or wrap setup in document.addEventListener('DOMContentLoaded', init). Prefer defer in the tag for a single script, and avoid async here because it gives no ordering guarantee relative to other scripts. Do not rely on window.onload, which also waits for images. Confirm the diagnosis by logging document.readyState at the top of the file: loading means the parser has not finished.