Advanced Web Programming
DOM, Events, Regular Expressions and Debugging
PGCP-AC
The browser turns HTML into a live object tree. JavaScript can query and modify that tree, respond to user and browser events, validate structured text and inspect failures with developer tools. Correct code must preserve security, accessibility and predictable event behavior while doing so.
1. A programmable document
The DOM represents a document as a tree of nodes. JavaScript can find nodes, inspect attributes, create elements and update the tree. querySelector returns the first matching element, while querySelectorAll returns a static collection of matches. A selector that finds nothing returns null in the first case, so dereferencing the result without checking can cause an exception. textContent treats input as text; innerHTML parses markup and must not be used carelessly with untrusted content.
Events travel through capture, target and usually bubble phases. target identifies the originating element; currentTarget identifies the element whose listener is running. Event delegation attaches a listener to an ancestor and interprets events from descendants, which is useful for dynamically added list items. preventDefault cancels a default action when cancellation is permitted; stopPropagation affects event travel rather than the default action.
2. Patterns and investigation
A regular expression describes a text pattern. Anchors constrain position, character classes describe alternatives, quantifiers describe repetition and flags modify matching. Validation should express the intended whole input rather than accidentally accept a matching substring. JavaScript exceptions can be handled with try and catch and finally supports cleanup. A linter such as JSLint reports suspicious constructs and style problems; it does not prove a program correct.
const list = document.querySelector('#items');
list.addEventListener('click', event => {
const button = event.target.closest('button');
if (button && list.contains(button)) button.remove();
});
const validCode = /^[A-Z]{3}\d{2}$/.test('ABC12');
Developer tools expose the DOM, styles, console, network exchanges and debugger. A breakpoint pauses at a chosen execution point, making it possible to inspect values and the call stack. When a form fails, inspect both the submitted request and the response before changing unrelated code.
3. Building a result safely
const item = document.createElement('li');
item.textContent = userSuppliedTitle;
document.querySelector('#results').appendChild(item);
This code creates a structural element but treats the supplied title as text. Removing a node changes the current DOM; it does not rewrite the original HTML file on disk. Setting an attribute changes markup-associated information, while properties can represent live state, such as whether a checkbox is currently checked.
A regular expression's i flag enables case-insensitive matching and g enables repeated global matching. A global regex's test method can change lastIndex, making repeated calls on the same regex object stateful. For simple whole-field validation, an anchored non-global pattern is often easier to reason about. A caught exception should produce a useful recovery path or diagnostic rather than an empty catch block that hides the fault.
4. Nodes, elements and traversal
The document is a tree containing document, element, text, comment and other node types. Element-focused APIs avoid unexpected whitespace text nodes when structural markup is the concern.
const form = document.querySelector('#search-form');
if (!form) throw new Error('Search form is missing');
const input = form.querySelector('[name="query"]');
const parent = input.parentElement;
const next = input.nextElementSibling;
querySelector returns the first matching element or null. querySelectorAll returns a static NodeList; later DOM additions do not automatically appear in that collection. Some older collection APIs return live collections, so know whether a collection is a snapshot before mutating while iterating.
closest(selector) searches the element and then its ancestors. matches(selector) tests one element. contains(node) confirms containment. These APIs are especially useful for delegated events.
5. Creating and updating content
Construct nodes explicitly when values come from users or services:
function createResult({ title, url }) {
const item = document.createElement('li');
const link = document.createElement('a');
link.textContent = title;
link.href = url;
item.append(link);
return item;
}
textContent treats a string as text. innerHTML asks the browser to parse a string as markup. If untrusted data reaches an HTML parsing sink, attackers may inject dangerous elements or attributes. Use text and property APIs for ordinary data. When an application intentionally accepts HTML, apply a reviewed sanitization policy rather than a homemade replacement expression.
Appending a DocumentFragment or using append with several nodes can group updates. Avoid repeated layout-dependent reads between writes in performance-sensitive code because they may force extra style and layout work.
6. Attributes, properties, classes and state
Attributes come from markup; properties expose current object state. They often reflect each other but are not identical. For a checkbox, checked reports live state while the checked attribute represents the default markup state.
checkbox.checked = true;
checkbox.setAttribute('aria-describedby', 'terms-help');
panel.classList.toggle('is-open', shouldOpen);
button.dataset.itemId = String(item.id);
Use classList instead of manually concatenating class strings. dataset maps data-* attributes to string-valued properties. Treat dataset content as untrusted when it ultimately came from outside the application.
7. Event propagation
An event path normally has three stages:
- Capture: the event travels from outer ancestors toward the target.
- Target: listeners associated with the target run.
- Bubble: a bubbling event travels back through ancestors.
event.target is the originating target. event.currentTarget is the object whose listener is currently executing. They are equal for a direct target listener but can differ during propagation.
preventDefault() requests cancellation of a cancelable default action, such as navigation or form submission. stopPropagation() stops travel to further nodes; it does not cancel the default. stopImmediatePropagation() also prevents later listeners on the same target and should be used only when that strong coupling is justified.
8. Event delegation
Delegation relies on bubbling and is valuable for dynamic collections:
const list = document.querySelector('#tasks');
list.addEventListener('click', event => {
const removeButton = event.target.closest('[data-action="remove"]');
if (!removeButton || !list.contains(removeButton)) return;
const item = removeButton.closest('li');
item?.remove();
});
closest handles clicks on an icon inside the button. The containment check prevents accidentally accepting a matching ancestor outside the delegated region. One listener can support items added after registration.
Not every event bubbles in the same way. focusin and focusout bubble, while focus and blur do not ordinarily bubble. Check event semantics instead of assuming delegation always works.
9. Listener lifetime and options
Keep the function reference when a listener must be removed:
function handleResize() { /* update layout state */ }
window.addEventListener('resize', handleResize);
window.removeEventListener('resize', handleResize);
A new arrow expression is a different function and cannot remove the original listener. The once option removes a listener after its first invocation. passive promises that a listener will not cancel the default, allowing selected scrolling optimizations. An AbortSignal can coordinate cleanup for several listeners.
10. Form and keyboard events
Handle form submission through submit, not only a button click, because users can submit using the keyboard or other controls. input fires as a value changes; change generally represents a committed change. Keyboard handling should use meaningful keys and should not replace native button behavior with custom key-code logic.
When an asynchronous operation starts, expose a status, avoid accidental duplicate submission and restore controls after failure. Preserve focus and announce result changes where appropriate.
11. Regular-expression building blocks
| Construct | Meaning |
|---|---|
^, $ | Beginning and end anchors |
[A-Z] | One character from a class/range |
\d | A digit character |
*, +, ? | Zero or more, one or more, optional |
{m,n} | Bounded repetition |
(group) | Grouping and capture |
(?:group) | Grouping without capture |
| `a | b` |
For a whole employee code, /^[A-Z]{2}-\d{4}$/ is clearer than a substring search. Escape literal metacharacters. A regular expression validates shape, not business truth: a date-shaped string may still represent an impossible date.
Flags modify behavior. i ignores case, m changes line-anchor handling, s allows dot to match line terminators and g searches repeatedly. A global or sticky regular expression maintains lastIndex; repeated test() calls can therefore alternate unexpectedly unless state is handled deliberately.
12. Exceptions and useful diagnostics
Use exceptions for failures that prevent the current operation from completing meaningfully:
try {
const payload = JSON.parse(text);
renderResults(payload.items);
} catch (error) {
console.error('Unable to render results', error);
showMessage('The response could not be processed.');
} finally {
spinner.hidden = true;
}
finally runs whether the protected operation completes or throws, making it suitable for cleanup. Do not silently swallow errors. A user-facing message and a developer-facing diagnostic have different audiences; avoid exposing confidential details or stack traces in the page.
13. A debugging workflow
- Reproduce the smallest failing case.
- Read the first relevant console error and its stack.
- Inspect the live DOM and computed styles rather than only source HTML.
- Pause at a breakpoint before the incorrect state appears.
- Inspect local values, the call stack and event-listener context.
- For requests, check method, URL, headers, body, status and response.
- Form a specific hypothesis and change one relevant factor.
- Add a regression test or reproducible check after repair.
Logging can reveal values but breakpoints preserve context and allow stepwise execution. A linter identifies rule-detectable defects and suspicious constructs; it cannot prove that requirements or algorithms are correct.
14. Complete interactive example
const form = document.querySelector('#task-form');
const input = form.querySelector('[name="task"]');
const list = document.querySelector('#task-list');
const taskPattern = /^.{2,80}$/u;
form.addEventListener('submit', event => {
event.preventDefault();
const title = input.value.trim();
if (!taskPattern.test(title)) {
input.setCustomValidity('Enter between 2 and 80 characters.');
input.reportValidity();
return;
}
input.setCustomValidity('');
const item = document.createElement('li');
const text = document.createElement('span');
const remove = document.createElement('button');
text.textContent = title;
remove.type = 'button';
remove.dataset.action = 'remove';
remove.textContent = `Remove ${title}`;
item.append(text, remove);
list.append(item);
form.reset();
input.focus();
});
The example handles the form event, validates the complete normalized value, inserts untrusted content as text, makes the removal button explicit and returns focus for efficient repeated entry. A delegated list listener can remove both existing and newly added tasks.
15. Practical considerations
querySelectorreturnsnullwhen no match exists.querySelectorAllreturns a static collection.innerHTMLparses markup;textContentdoes not.targetandcurrentTargetcan identify different elements.- Stopping propagation does not cancel default behavior.
- Delegation depends on event propagation and careful target matching.
- A nonempty custom validity message keeps a control invalid.
- Anchors are needed when a regex must validate the entire string.
- Global regex testing can change
lastIndex. - A linter cannot establish program correctness.
Continue learning
Related notes
Put this topic into timed practice
Open mock tests when you want full-exam pacing, or keep drilling in practice mode.