Lekcja 10: Manipulacja DOM

Po wybraniu elementów DOM za pomocą metod poznanych w poprzedniej lekcji, możemy dynamicznie modyfikować ich zawartość, atrybuty i style za pomocą JavaScript. To pozwala na tworzenie interaktywnych i dynamicznych stron internetowych.

Modyfikowanie zawartości elementów

Istnieje kilka właściwości do odczytu i zmiany zawartości HTML i tekstowej elementów.

<div id="container">
    <p>To jest <strong>pierwszy</strong> paragraf.</p>
</div>
// Plik HTML musi zawierać powyższy div
let container = document.getElementById("container");
let paragraph = container.querySelector("p");

// Odczyt zawartości
if (paragraph) {
    console.log("innerHTML:", paragraph.innerHTML); // "To jest pierwszy paragraf."
    console.log("textContent:", paragraph.textContent); // "To jest pierwszy paragraf."
    console.log("innerText:", paragraph.innerText); // "To jest pierwszy paragraf."

    // Przykład zmiany tylko tekstu wewnątrz strong
    let strongElement = paragraph.querySelector("strong");
    if (strongElement) {
        strongElement.textContent = "zmodyfikowany";
    }
    console.log("Po zmianie textContent strong:", paragraph.innerHTML);
    // Wynik: "To jest zmodyfikowany paragraf."
}

// Modyfikacja zawartości (przykłady zakomentowane)
// if (paragraph) {
//     paragraph.innerHTML = "Nowa zawartość z <em>znacznikiem</em>.";
//     // paragraph.textContent = "Nowa zawartość tekstowa (znaczniki zostaną potraktowane jak tekst).";
// }

Uwaga na bezpieczeństwo: Ustawianie innerHTML na podstawie danych pochodzących od użytkownika bez odpowiedniego oczyszczenia może prowadzić do ataków Cross-Site Scripting (XSS). Jeśli wstawiasz tylko tekst, zawsze używaj textContent.

Modyfikowanie atrybutów

Możemy odczytywać, ustawiać i usuwać atrybuty elementów HTML.

Dla niektórych popularnych atrybutów (np. id, src, href, class, value) istnieją również bezpośrednie właściwości obiektu elementu.

<a id="link" href="https://example.com" target="_blank">Przykładowy link</a>
<img id="obrazek" src="stary.jpg" alt="Opis obrazka">
// Plik HTML musi zawierać powyższe elementy
let link = document.getElementById("link");
let image = document.getElementById("obrazek");

if (link) {
    // Odczyt atrybutów
    console.log("href linku:", link.getAttribute("href")); // "https://example.com"
    console.log("target linku:", link.target); // "_blank" (bezpośrednia właściwość)

    // Modyfikacja atrybutów
    link.setAttribute("href", "https://nowy-adres.pl");
    link.textContent = "Nowy link";

    // Sprawdzenie i usunięcie atrybutu
    if (link.hasAttribute("target")) {
        link.removeAttribute("target");
        console.log("Usunięto atrybut target.");
    }

    // Dodanie nowego atrybutu
    link.setAttribute("data-custom", "jakas-wartosc");
    console.log("Nowy atrybut data-custom:", link.getAttribute("data-custom"));
}

if (image) {
    console.log("src obrazka:", image.src); // Pełny URL do "stary.jpg"
    // Modyfikacja atrybutów
    image.src = "nowy.png"; // Użycie bezpośredniej właściwości
    image.setAttribute("alt", "Nowy opis obrazka");
}

Modyfikowanie stylów CSS

Style CSS elementów można modyfikować na dwa główne sposoby:

1. Właściwość element.style

Pozwala na bezpośredni dostęp i modyfikację stylów inline elementu (tych zdefiniowanych w atrybucie style="..."). Nazwy właściwości CSS są konwertowane na camelCase (np. background-color staje się backgroundColor).

<div id="box" style="width: 100px; height: 100px; border: 1px solid black;"></div>
// Plik HTML musi zawierać powyższy div
let box = document.getElementById("box");

if (box) {
    // Odczyt stylu (tylko inline)
    console.log("Szerokość boxa (inline):", box.style.width); // "100px"

    // Modyfikacja stylów
    box.style.backgroundColor = "lightblue";
    box.style.borderWidth = "3px";
    box.style.borderColor = "blue";
    box.style.borderRadius = "10px";

    // Usunięcie stylu (ustawienie na pusty string)
    // box.style.borderRadius = "";
}

Ograniczenie: element.style odczytuje tylko style ustawione bezpośrednio w atrybucie style elementu, a nie te pochodzące z arkuszy CSS.

2. Manipulacja klasami CSS (element.classList)

Jest to preferowany sposób na zmianę wyglądu elementów. Zamiast manipulować pojedynczymi stylami, dodajemy lub usuwamy predefiniowane klasy CSS.

/* W pliku CSS */
.highlight {
    background-color: yellow;
    font-weight: bold;
}

.error {
    border: 2px solid red;
    color: red;
}

.text-large { /* Przykładowa klasa */
    font-size: 1.2em;
}
<p id="message">To jest ważna wiadomość.</p>
// Plik HTML musi zawierać powyższy paragraf
let message = document.getElementById("message");

if (message) {
    // Dodanie klasy
    message.classList.add("highlight");

    // Sprawdzenie, czy klasa istnieje
    console.log("Czy ma klasę highlight?", message.classList.contains("highlight")); // true

    // Dodanie kolejnej klasy
    message.classList.add("text-large");

    // Usunięcie klasy
    // message.classList.remove("text-large");

    // Przełączenie klasy (toggle)
    // Załóżmy, że chcemy włączać/wyłączać klasę 'error'
    // message.classList.toggle("error"); // Jeśli nie ma 'error', doda ją. Jeśli jest, usunie.

    // Zastąpienie klasy
    // message.classList.replace("highlight", "normal-priority");
}

Tworzenie i dodawanie nowych elementów

<ul id="lista">
    <li>Element 1</li>
</ul>
// Plik HTML musi zawierać powyższą listę
let lista = document.getElementById("lista");

if (lista) {
    // 1. Utwórz nowy element li
    let nowyElement = document.createElement("li");

    // 2. Utwórz tekst dla nowego elementu
    let tekstElementu = document.createTextNode("Nowy Element 2");

    // 3. Dodaj tekst do elementu li
    nowyElement.appendChild(tekstElementu);

    // 4. Dodaj nowy element li na koniec listy ul
    lista.appendChild(nowyElement);

    // Przykład wstawienia przed istniejącym elementem
    let pierwszyElement = lista.querySelector("li"); // Znajdź pierwszy element li
    let wstawianyElement = document.createElement("li");
    wstawianyElement.textContent = "Element 0 (wstawiony)";
    if (pierwszyElement) {
        lista.insertBefore(wstawianyElement, pierwszyElement);
    }

    // Przykład usunięcia elementu
    // let elementDoUsuniecia = lista.lastElementChild; // Ostatni element li
    // if (elementDoUsuniecia) {
    //     lista.removeChild(elementDoUsuniecia);
    // }
}

Zadanie praktyczne

Masz poniższy HTML. Napisz skrypt, który:

  1. Zmieni tekst wewnątrz elementu h1 na "Zmieniony Nagłówek".
  2. Doda klasę active do pierwszego elementu listy.
  3. Zmieni atrybut src obrazka na "logo.png".
  4. Doda nowy element li z tekstem "Element 4" na koniec listy.
<h1 id="title">Oryginalny Nagłówek</h1>
<ul id="items">
    <li>Element 1</li>
    <li>Element 2</li>
    <li>Element 3</li>
</ul>
<img id="logo" src="old_logo.jpg" alt="logo">
Pokaż rozwiązanie
document.addEventListener("DOMContentLoaded", () => {
    // 1. Zmień tekst h1
    let title = document.getElementById("title");
    if (title) {
        title.textContent = "Zmieniony Nagłówek";
    }

    // 2. Dodaj klasę do pierwszego li
    let itemsList = document.getElementById("items");
    if (itemsList) {
        let firstItem = itemsList.querySelector("li"); // lub itemsList.firstElementChild
        if (firstItem) {
            firstItem.classList.add("active");
        }

        // 4. Dodaj nowy element li
        let newItem = document.createElement("li");
        newItem.textContent = "Element 4";
        itemsList.appendChild(newItem);
    }

    // 3. Zmień src obrazka
    let logo = document.getElementById("logo");
    if (logo) {
        logo.setAttribute("src", "logo.png");
        // lub logo.src = "logo.png";
    }
});

Zadanie do samodzielnego wykonania

Stwórz przycisk HTML. Napisz skrypt, który po kliknięciu tego przycisku (na razie bez obsługi zdarzeń, po prostu wykonaj kod) stworzy nowy paragraf <p> z tekstem "Nowy paragraf został dodany!" i doda go na końcu elementu body dokumentu.

FAQ - Manipulacja DOM

Kiedy używać `innerHTML`, a kiedy `textContent`?

Używaj textContent, gdy chcesz bezpiecznie wstawić lub odczytać tylko tekst, bez interpretacji HTML. Używaj innerHTML, gdy świadomie chcesz wstawić strukturę HTML. Pamiętaj o ryzyku XSS przy innerHTML, jeśli dane pochodzą z niezaufanego źródła.

Jaka jest różnica między `setAttribute` a bezpośrednim przypisaniem do właściwości (np. `img.src`)?

setAttribute działa na poziomie atrybutów HTML. Bezpośrednie właściwości (jak src, href, id, className) są właściwościami obiektu DOM. W większości przypadków działają podobnie, ale mogą istnieć subtelne różnice (np. img.src zwraca pełny URL, a getAttribute("src") oryginalną wartość atrybutu).

Czy manipulacja DOM jest kosztowna wydajnościowo?

Tak, częste i rozległe modyfikacje DOM mogą być kosztowne, ponieważ przeglądarka musi przeliczyć układ strony (reflow) i ją przemalować (repaint). Staraj się minimalizować liczbę operacji na DOM, np. grupując zmiany lub modyfikując elementy poza głównym drzewem DOM przed ich wstawieniem.

Jak usunąć wszystkie dzieci elementu?

Najprostszym sposobem jest ustawienie innerHTML na pusty string: element.innerHTML = ";. Alternatywnie, można iterować w pętli, usuwając ostatnie dziecko, dopóki element nie będzie pusty: while (element.firstChild) { element.removeChild(element.firstChild); }.

Co to jest DocumentFragment?

DocumentFragment to lekki węzeł DOM, który działa jak tymczasowy kontener. Można do niego dodawać inne węzły (np. wiele elementów li), a następnie dodać cały fragment do głównego DOM za pomocą jednej operacji appendChild. Jest to wydajniejsze niż dodawanie wielu elementów pojedynczo.

Czy mogę klonować elementy DOM?

Tak, metoda element.cloneNode(deep) tworzy kopię węzła. Jeśli argument deep jest ustawiony na true, kopiowane są również wszystkie dzieci węzła (rekurencyjnie). Jeśli false (domyślnie), kopiowany jest tylko sam węzeł bez dzieci.