Skillversitas

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.