30. Selecting DOM Elements
Selecting DOM elements is the first step before manipulating them. JavaScript provides several methods to find elements by ID, class, tag name, or CSS selector.
Code Example
getElementById
// Only for IDs — returns 1 element or null
const header = document.getElementById("header");
console.log(header); // <header id="header">...</header>
// Case-sensitive for IDs
const wrong = document.getElementById("Header"); // null
// Tip: if not found, always check for null
const data = document.getElementById("data-student");
if (data) {
data.textContent = "Data found";
} else {
console.error("#data-student element not found!");
}
getElementsByClassName
// Returns an HTMLCollection (live!) — updates automatically when the DOM changes
const cards = document.getElementsByClassName("card");
console.log(cards.length); // number of elements with the class "card"
console.log(cards[0]); // first element
// HTMLCollection is not an array — you must convert it first to use array methods
const cardsArray = Array.from(cards);
cardsArray.forEach(card => {
console.log(card.textContent);
});
// Live collection — updates automatically
document.body.innerHTML += '<div class="card">New</div>';
console.log(cards.length); // increases automatically!
getElementsByTagName
// Returns an HTMLCollection of all elements with a given tag
const paragraphs = document.getElementsByTagName("p");
console.log(paragraphs.length);
// "all" returns every element
const allElements = document.getElementsByTagName("*");
console.log(`Total elements on page: ${allElements.length}`);
querySelector — Most Flexible
// Returns the FIRST element matching a CSS selector
const first = document.querySelector(".card");
console.log(first); // first card
// Complex selectors
const btnSubmit = document.querySelector('form#list button[type="submit"]');
const active = document.querySelector('nav a.active');
const inputPertama = document.querySelector('input[name="email"]');
// Compound selectors
const cardHeader = document.querySelector('.card .header');
const lastItem = document.querySelector('ul li:last-child');
const firstInput = document.querySelector('.form-group:first-child input');
// Null if not found
const missing = document.querySelector(".missing-class");
console.log(missing); // null
querySelectorAll — All Matches
// Returns a NodeList (static — a snapshot, not live)
const allCards = document.querySelectorAll(".card");
console.log(allCards.length);
// You can use forEach directly (unlike HTMLCollection)
allCards.forEach(card => {
card.style.border = "1px solid #ccc";
});
// Filter with Array methods
const NodeList = document.querySelectorAll("li");
const arrayLi = Array.from(NodeList);
const active = arrayLi.filter(li => li.classList.contains("active"));
const large = arrayLi.filter(li => li.offsetHeight > 100);
// Query from a specific context (not the entire document)
const sidebar = document.querySelector("#sidebar");
const linksInSidebar = sidebar.querySelectorAll("a");
console.log(linksInSidebar.length); // only links in the sidebar
closest — Find the Nearest Element Upward
// Start from a given element and find the nearest matching parent
document.addEventListener("click", (event) => {
// Find the nearest card from the clicked element
const card = event.target.closest(".card");
if (card) {
console.log("Click inside card:", card.id);
}
// Find the nearest form
const form = event.target.closest("form");
if (form) {
console.log("Click inside form:", form.id);
}
});
// Example: event delegation for a dynamic list
const studentList = document.getElementById("list-student");
studentList.addEventListener("click", (event) => {
const item = event.target.closest(".student-item");
if (item) {
const studentId = item.dataset.studentId;
console.log(`Clicked student: ${studentId}`);
}
});
matches — Check Whether an Element Matches a Selector
const button = document.querySelector("#btnSubmit");
// Check whether the button has a specific class
if (button.matches(".btn-primary")) {
console.log("This is the main button");
}
// Used in filtering
const allButtons = document.querySelectorAll("button");
const primaryButton = Array.from(allButtons)
.filter(btn => btn.matches(".btn-primary"));
// Example: validation in an event handler
function handleKeydown(event) {
const input = event.target;
if (input.matches('input[type="email"]')) {
console.log("Validating email...");
} else if (input.matches('input[name="studentId"]')) {
console.log("Validating StudentID...");
}
}
document.addEventListener("input", handleKeydown);
Comparison of All Selection Methods
// getElementById — fast, IDs only
const el1 = document.getElementById("myId");
// getElementsByClassName — live HTMLCollection
const el2 = document.getElementsByClassName("myClass");
// getElementsByTagName — live HTMLCollection
const el3 = document.getElementsByTagName("div");
// querySelector — CSS selector, first match only
const el4 = document.querySelector("#myId .card > p");
// querySelectorAll — CSS selector, all matches, static NodeList
const el5 = document.querySelectorAll(".card");
// Performance comparison:
// getElementById > getElementsByClassName > getElementsByTagName
// > querySelector > querySelectorAll
Code Explanation
| Method | Return | Live? | Selector |
|---|---|---|---|
getElementById() |
Element or null | N/A | ID only |
getElementsByClassName() |
HTMLCollection | ✅ Live | Class name |
getElementsByTagName() |
HTMLCollection | ✅ Live | Tag name |
querySelector() |
Element or null | N/A | CSS selector |
querySelectorAll() |
NodeList | ❌ Static | CSS selector |
closest() |
Element or null | N/A | CSS selector (upward) |
matches() |
boolean | N/A | CSS selector |
HTMLCollection vs NodeList
HTMLCollection (live):
- from getElementsByClassName/TagName
- updates automatically when the DOM changes
- does NOT have forEach (you need Array.from())
NodeList (static):
- from querySelectorAll
- a snapshot, doesn't update automatically
- has forEach()
Real-World Examples
Manipulating a student registration form:
document.addEventListener("DOMContentLoaded", () => {
// 1. Select all form elements
const form = document.querySelector("#registrationForm");
const inputs = form.querySelectorAll("input[required]");
const selectMajor = form.querySelector('select[name="major"]');
const btnSubmit = form.querySelector('button[type="submit"]');
const errorContainer = form.querySelector(".error-messages");
// 2. Show a summary when input changes
inputs.forEach(input => {
input.addEventListener("input", () => {
const label = form.querySelector(`label[for="${input.id}"]`);
const labelText = label?.textContent ?? input.name;
if (input.value.trim()) {
input.classList.add("filled");
input.classList.remove("error");
} else {
input.classList.remove("filled");
}
});
});
// 3. Event delegation for dynamic error messages
errorContainer.addEventListener("click", (event) => {
const errorMsg = event.target.closest(".error-msg");
if (errorMsg) {
errorMsg.remove();
}
});
// 4. Check form status with matches
function checkStatusForm() {
const allFilled = Array.from(inputs).every(
input => input.value.trim() !== ""
);
const majorSelected = selectMajor.value !== "";
if (allFilled && majorSelected) {
btnSubmit.disabled = false;
btnSubmit.classList.remove("btn-disabled");
} else {
btnSubmit.disabled = true;
btnSubmit.classList.add("btn-disabled");
}
}
form.addEventListener("input", checkStatusForm);
form.addEventListener("change", checkStatusForm);
// 5. Access data attributes for filtering
const listMajor = document.querySelectorAll(".major-card");
const filterIntake = document.querySelector("#filterIntake");
filterIntake?.addEventListener("change", (event) => {
const year = event.target.value;
listMajor.forEach(card => {
const intakeCard = card.dataset.year;
if (!year || intakeCard === year) {
card.style.display = "";
} else {
card.style.display = "none";
}
});
});
});
Example: DOM Traversal for a Tab Component
class TabComponent {
constructor(container) {
this.container = document.querySelector(container);
if (!this.container) return;
this.tabs = this.container.querySelectorAll(".tab-nav button");
this.panels = this.container.querySelectorAll(".tab-panel");
this.init();
}
init() {
this.tabs.forEach((tab, index) => {
tab.addEventListener("click", () => this.switchTab(index));
});
// Activate the first tab
this.switchTab(0);
}
switchTab(activeIndex) {
// Deactivate all
this.tabs.forEach(tab => tab.classList.remove("active"));
this.panels.forEach(panel => panel.classList.remove("active"));
// Activate the selected one
this.tabs[activeIndex].classList.add("active");
this.panels[activeIndex].classList.add("active");
}
}
// Usage
document.addEventListener("DOMContentLoaded", () => {
new TabComponent("#profileTabs");
});
Conclusion
getElementById— the fastest; use it for unique IDs.querySelector/querySelectorAll— the most flexible; full CSS selector support.closest— traverses upward to find the nearest parent — very useful for event delegation.matches— checks whether an element matches a selector without needing a reference to another element.- HTMLCollection (live) vs NodeList (static): know the difference because behavior differs when the DOM changes.