JavaScript: DOM, događaji i interaktivne stranice

Lekcija 10 od 17 · HTML, CSS i JavaScript

Pregledač od HTML-a pravi stablo objekata – DOM. JavaScript preko DOM-a pronalazi elemente, menja tekst, stil i klase, pravi nove elemente i reaguje na klikove i unos. Na kraju lekcije pravimo malu aplikaciju „Lista zadataka“ koja pamti podatke i posle zatvaranja pregledača.

Pre nego što počneš: potrebno predznanje – JavaScript – prvi koraci.
Kako da učiš: zadatke na kraju prvo uradi sam/a u svom editoru; uputstvo i rešenje otvori tek posle toga. Svaki primer ima kod i živi rezultat ispod njega; kod prekucaj u svoj fajl i menjaj ga.

DOM: stranica kao stablo

document
└── html
    ├── head
    │   └── title → "Lista"
    └── body
        ├── h1 → "Moji zadaci"
        ├── input#novi
        ├── button#dodaj
        └── ul#lista
            ├── li → "Nauči DOM"
            └── li → "Uradi domaći"
Pronalaženje elemenataVraća
document.getElementById("lista")element sa id="lista"
document.querySelector(".kartica")prvi element koji odgovara CSS selektoru
document.querySelectorAll("li")sve takve elemente (lista po kojoj se ide sa for...of)

Menjanje sadržaja, stila i klasa

<style>
  .tamno { background: #222; color: #eee; }
  .kutija { padding: 12px; border-radius: 8px; transition: background 0.3s; }
</style>
<div id="k" class="kutija">
  <h3 id="naslov">Naslov</h3>
  <p>Klikni dugmad:</p>
  <button id="b1">Promeni tekst</button>
  <button id="b2">Tamna tema</button>
  <button id="b3">Dodaj stavku</button>
  <ul id="lista"></ul>
</div>
<script>
  const naslov = document.getElementById("naslov");
  const kutija = document.getElementById("k");
  const lista  = document.getElementById("lista");
  let n = 0;

  document.getElementById("b1").addEventListener("click", () => {
    naslov.textContent = "Tekst promenjen u " + new Date().toLocaleTimeString("sr");
  });
  document.getElementById("b2").addEventListener("click", () => {
    kutija.classList.toggle("tamno");          // dodaj klasu ako je nema, ukloni ako je ima
  });
  document.getElementById("b3").addEventListener("click", () => {
    n++;
    const li = document.createElement("li");   // novi element
    li.textContent = "Stavka " + n;
    lista.appendChild(li);                     // ubaci ga u listu
  });
</script>

Rezultat:

NaredbaŠta radi
el.textContent = "..."menja tekst elementa
el.style.color = "red"menja jedno CSS svojstvo (imena sa crticom pišu se spojeno: backgroundColor)
el.classList.add / remove / toggle("ime")dodaje, uklanja, prebacuje klasu – bolje od menjanja stila jedan po jedan
document.createElement("li") + roditelj.appendChild(el)pravi i ubacuje novi element
el.remove()briše element
input.valuetekst upisan u polje
Bezbednost: tekst koji je upisao korisnik ubacuj sa textContent, a ne sa innerHTML. Sa innerHTML bi se upisani <script> ili <img onerror=...> izvršio – to je XSS napad iz lekcije Bezbednost web aplikacija.

Događaji

Događaj (event) je nešto što se desi na stranici. Funkcija koja se poziva kada se događaj desi zove se osluškivač (event listener):

element.addEventListener("click", function (e) {
  // e = podaci o događaju (npr. e.key za taster, e.target za element)
});
DogađajKada se desi
clickklik mišem ili dodir
inputsvaka promena teksta u polju
changepromena izbora (select, checkbox)
submitslanje forme
keydownpritisak tastera (e.key = "Enter", "ArrowUp"…)
mouseover / mouseoutmiš ulazi / izlazi
DOMContentLoadedHTML je učitan (umesto ovoga može defer)
<label>Upiši ime: <input id="ime" type="text"></label>
<p id="pozdrav">Zdravo, ...!</p>
<label>Broj: <input id="broj" type="number" value="5"></label>
<p id="kvadrat"></p>
<script>
  const ime = document.getElementById("ime");
  ime.addEventListener("input", () => {
    document.getElementById("pozdrav").textContent = "Zdravo, " + (ime.value || "...") + "!";
  });

  const broj = document.getElementById("broj");
  function racunaj() {
    const x = Number(broj.value);          // tekst iz polja -> broj
    document.getElementById("kvadrat").textContent = `${x}² = ${x * x}`;
  }
  broj.addEventListener("input", racunaj);
  racunaj();                               // izračunaj odmah na početku
</script>

Rezultat:

Obrada forme u JavaScript-u

<form id="f">
  <label>Stranica a: <input name="a" type="number" required></label>
  <label>Stranica b: <input name="b" type="number" required></label>
  <button>Izračunaj</button>
</form>
<p id="rez"></p>
<script>
  document.getElementById("f").addEventListener("submit", e => {
    e.preventDefault();                    // NE šalji formu serveru i ne osvežavaj stranicu
    const a = Number(e.target.a.value);
    const b = Number(e.target.b.value);
    if (a <= 0 || b <= 0) {
      document.getElementById("rez").textContent = "Stranice moraju biti pozitivne.";
      return;
    }
    document.getElementById("rez").textContent = `Obim: ${2 * (a + b)}, površina: ${a * b}`;
  });
</script>

Rezultat:

e.preventDefault() zaustavlja podrazumevano ponašanje (slanje forme i osvežavanje stranice) – tako JavaScript sam obradi podatke.

Mini projekat: lista zadataka koja pamti

<style>
  body { font-family: system-ui, Arial, sans-serif; }
  .unos { display: flex; gap: 6px; }
  .unos input { flex: 1; padding: 6px; }
  ul { padding: 0; list-style: none; }
  li { display: flex; align-items: center; gap: 8px; padding: 6px; border-bottom: 1px solid #ddd; }
  li span { flex: 1; cursor: pointer; }
  li.gotovo span { text-decoration: line-through; color: #999; }
  li button { border: 0; background: none; cursor: pointer; font-size: 1.1rem; }
</style>
<h3>Moji zadaci</h3>
<form class="unos" id="forma">
  <input id="novi" placeholder="Novi zadatak..." autocomplete="off">
  <button>Dodaj</button>
</form>
<ul id="lista"></ul>
<p id="brojac"></p>
<script>
  // podaci: niz objekata { tekst, gotovo }
  let zadaci = [];
  try { zadaci = JSON.parse(localStorage.getItem("zadaci")) || []; } catch (e) {}

  function sacuvaj() {
    try { localStorage.setItem("zadaci", JSON.stringify(zadaci)); } catch (e) {}
  }

  function prikazi() {
    const lista = document.getElementById("lista");
    lista.innerHTML = "";                        // obriši staru listu (bez korisničkog teksta)
    zadaci.forEach((z, i) => {
      const li = document.createElement("li");
      if (z.gotovo) li.classList.add("gotovo");
      const tekst = document.createElement("span");
      tekst.textContent = z.tekst;               // textContent – bezbedno
      tekst.addEventListener("click", () => { z.gotovo = !z.gotovo; sacuvaj(); prikazi(); });
      const obrisi = document.createElement("button");
      obrisi.textContent = "✕";
      obrisi.title = "Obriši";
      obrisi.addEventListener("click", () => { zadaci.splice(i, 1); sacuvaj(); prikazi(); });
      li.append(tekst, obrisi);
      lista.appendChild(li);
    });
    const ostalo = zadaci.filter(z => !z.gotovo).length;
    document.getElementById("brojac").textContent = `Preostalo: ${ostalo} od ${zadaci.length}`;
  }

  document.getElementById("forma").addEventListener("submit", e => {
    e.preventDefault();
    const polje = document.getElementById("novi");
    const tekst = polje.value.trim();
    if (tekst === "") return;
    zadaci.push({ tekst: tekst, gotovo: false });
    polje.value = "";
    sacuvaj();
    prikazi();
  });

  prikazi();
</script>

Rezultat:

Ključne ideje:

  • podaci odvojeni od prikaza – sve je u nizu zadaci; posle svake promene funkcija prikazi() ponovo nacrta listu iz niza;
  • localStorage – pregledač čuva tekst pod ključem i posle zatvaranja; niz se pretvara u tekst sa JSON.stringify, a nazad sa JSON.parse (isti JSON kao u lekciji API i JSON);
  • localStorage pripada jednom pregledaču na jednom uređaju – za podatke koje vide drugi korisnici potreban je backend sa bazom.

Ova aplikacija je ista ideja kao To-do aplikacija u Flask-u, samo što tamo podatke čuva server.

Zadaci za vežbu

Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno ispravna u pregledaču.

ZADATAK 01Težina: lako

Promena teme dugmetom

Napravi dugme „Tamna tema“ koje prebacuje stranicu u tamnu temu i nazad, a tekst dugmeta se menja u „Svetla tema“ i obrnuto. Izbor se pamti u localStorage-u.

ZADATAK 02Težina: srednje

Igra „Pogodi broj“

Računar zamisli broj od 1 do 100. Korisnik upisuje pokušaj u polje i klikne „Proveri“; ispod se ispisuje „Više“, „Manje“ ili „Pogodak u N pokušaja!“. Dugme „Nova igra“ zamišlja novi broj.

Česte greške i saveti
  • Skripta koja traži element pre nego što je učitan dobija null – stavi <script> na kraj body ili koristi defer.
  • Vrednost iz polja je uvek tekst – pretvori je sa Number().
  • U formi obrađenoj JavaScript-om ne zaboravi e.preventDefault(), inače se stranica osveži.
  • Korisnički tekst ubacuj sa textContent, ne sa innerHTML.

Proveri znanje

Otvori kviz

DOM i događaji

1. Šta vraća document.querySelector(".kartica")?




2. Kako bezbedno upisati tekst koji je uneo korisnik u element?



3. Čemu služi e.preventDefault() u događaju submit?




4. Gde localStorage čuva podatke?