Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Tworzenie interaktywnych komponentów

W centrum kontroli Parku Jurajskiego każdy panel na ekranie to osobny komponent - karta dinozaura, okno alertu, formularz raportu. Każdy z nich reaguje na działania operatora: kliknięcia, klawisze, najazd myszą. Budowanie takich interaktywnych komponentów to jeden z najczęstszych scenariuszy użycia event listenerów w prawdziwych aplikacjach.

W tej lekcji nauczysz się tworzyć samodzielne, wielokrotnego użytku komponenty UI, które zarządzają własnymi wydarzeniami - od okien modalnych po panele z dynamiczną zawartością.

Komponent: Okno modalne (Modal)

Modal to okno wyświetlane na wierzchu strony, blokujące interakcję z resztą interfejsu. W naszym parku użyjemy go do wyświetlania szczegółów dinozaura po kliknięciu jego karty.

Tworzenie struktury i rejestracja wydarzeń

1function createDinosaurModal(dinosaurData) {
2  // Tworzenie struktury modala
3  const modal = document.createElement('div');
4  modal.className = 'dinosaur-modal';
5  modal.innerHTML = `
6    <div class="modal-content">
7      <div class="modal-header">
8        <h2>${dinosaurData.name}</h2>
9        <button class="close-btn">&times;</button>
10      </div>
11      <div class="modal-body">
12        <img src="${dinosaurData.image}" alt="${dinosaurData.name}">
13        <p><strong>Era:</strong> ${dinosaurData.era}</p>
14        <p><strong>Dieta:</strong> ${dinosaurData.diet}</p>
15        <p><strong>Długość:</strong> ${dinosaurData.length}m</p>
16        <p><strong>Waga:</strong> ${dinosaurData.weight}kg</p>
17        <p>${dinosaurData.description}</p>
18      </div>
19      <div class="modal-footer">
20        <button class="book-tour-btn">Zarezerwuj wycieczkę</button>
21        <button class="add-favorite-btn">Dodaj do ulubionych</button>
22      </div>
23    </div>
24  `;
25
26  // Pobierz elementy interaktywne z modala
27  const closeBtn = modal.querySelector('.close-btn');
28  const bookTourBtn = modal.querySelector('.book-tour-btn');
29  const addFavoriteBtn = modal.querySelector('.add-favorite-btn');
30
31  // Zamykanie modala przyciskiem X
32  closeBtn.addEventListener('click', () => {
33    closeModal(modal);
34  });
35
36  // Zamykanie kliknięciem w tło (overlay)
37  modal.addEventListener('click', (e) => {
38    if (e.target === modal) {
39      closeModal(modal);
40    }
41  });
42
43  // Rezerwacja wycieczki
44  bookTourBtn.addEventListener('click', () => {
45    openBookingForm(dinosaurData.id);
46  });
47
48  // Dodawanie do ulubionych ze zmianą stanu przycisku
49  addFavoriteBtn.addEventListener('click', () => {
50    addToFavorites(dinosaurData.id);
51    addFavoriteBtn.textContent = 'Dodano do ulubionych!';
52    addFavoriteBtn.disabled = true;
53  });
54
55  // Zamykanie klawiszem Escape
56  const handleEscape = (e) => {
57    if (e.key === 'Escape') {
58      closeModal(modal);
59      document.removeEventListener('keydown', handleEscape);
60    }
61  };
62  document.addEventListener('keydown', handleEscape);
63
64  return modal;
65}
66
67function closeModal(modal) {
68  modal.classList.add('closing'); // Animacja zamykania
69  setTimeout(() => modal.remove(), 300);
70}

Zwróć uwagę na kilka kluczowych praktyk:

  • Zamykanie na Escape - dodajemy listener na
    document
    , ale usuwamy go gdy modal znika (zapobiega wyciekom pamięci)
  • Kliknięcie w tło - sprawdzamy
    e.target === modal
    żeby odróżnić kliknięcie w overlay od kliknięcia w treść modala
  • Zmiana stanu przycisku - po dodaniu do ulubionych blokujemy przycisk żeby zapobiec wielokrotnemu kliknięciu

Komponent: Przełącznik (Toggle Switch)

Prosty komponent do włączania i wyłączania systemów parku:

1function createToggle(label, onChange) {
2  const container = document.createElement('div');
3  container.className = 'toggle-container';
4  container.innerHTML = `
5    <span class="toggle-label">${label}</span>
6    <button class="toggle-btn" data-active="false">OFF</button>
7  `;
8
9  const btn = container.querySelector('.toggle-btn');
10
11  btn.addEventListener('click', () => {
12    const isActive = btn.dataset.active === 'true';
13    const newState = !isActive;
14
15    btn.dataset.active = String(newState);
16    btn.textContent = newState ? 'ON' : 'OFF';
17    btn.classList.toggle('active', newState);
18
19    // Wywołaj callback z nowym stanem
20    onChange(newState);
21  });
22
23  return container;
24}
25
26// Użycie - panel sterowania ogrodzeniami
27const fenceToggle = createToggle('Ogrodzenie elektryczne', (active) => {
28  console.log(`Ogrodzenie ${active ? 'AKTYWNE' : 'WYŁĄCZONE'}`);
29});
30document.getElementById('controls').appendChild(fenceToggle);

Komponent: Tabs (Zakładki)

Zakładki to niezbędny element panelu monitoringu, pozwalający przełączać między widokami różnych stref parku:

1function createTabs(tabsData) {
2  const container = document.createElement('div');
3  container.className = 'tabs-container';
4
5  // Nagłówki zakładek
6  const header = document.createElement('div');
7  header.className = 'tabs-header';
8
9  // Treści zakładek
10  const body = document.createElement('div');
11  body.className = 'tabs-body';
12
13  tabsData.forEach((tab, index) => {
14    // Przycisk zakładki
15    const tabBtn = document.createElement('button');
16    tabBtn.className = 'tab-btn' + (index === 0 ? ' active' : '');
17    tabBtn.textContent = tab.title;
18    tabBtn.dataset.index = index;
19
20    // Panel z treścią
21    const tabPanel = document.createElement('div');
22    tabPanel.className = 'tab-panel' + (index === 0 ? ' active' : '');
23    tabPanel.innerHTML = tab.content;
24
25    // Kliknięcie w zakładkę
26    tabBtn.addEventListener('click', () => {
27      // Dezaktywuj wszystkie
28      header.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
29      body.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
30
31      // Aktywuj wybraną
32      tabBtn.classList.add('active');
33      tabPanel.classList.add('active');
34    });
35
36    header.appendChild(tabBtn);
37    body.appendChild(tabPanel);
38  });
39
40  container.appendChild(header);
41  container.appendChild(body);
42  return container;
43}
44
45// Użycie
46const monitorTabs = createTabs([
47  { title: 'Strefa A', content: '<p>T-Rex: status OK</p>' },
48  { title: 'Strefa B', content: '<p>Raptory: aktywne</p>' },
49  { title: 'Strefa C', content: '<p>Brachiozaur: spokojny</p>' },
50]);

Wzorzec: Sprzątanie komponentu

Każdy komponent powinien mieć metodę

destroy()
, która usuwa wszystkie event listenery i zapobiega wyciekom pamięci:

1class ParkWidget {
2  constructor(element) {
3    this.element = element;
4    this.handlers = [];
5    this.init();
6  }
7
8  addListener(target, event, handler) {
9    target.addEventListener(event, handler);
10    this.handlers.push({ target, event, handler });
11  }
12
13  destroy() {
14    // Usunięcie wszystkich zarejestrowanych listenerów
15    this.handlers.forEach(({ target, event, handler }) => {
16      target.removeEventListener(event, handler);
17    });
18    this.handlers = [];
19    this.element.remove();
20  }
21
22  init() {
23    this.addListener(this.element, 'click', (e) => {
24      console.log('Widget kliknięty');
25    });
26    this.addListener(document, 'keydown', (e) => {
27      if (e.key === 'Escape') this.destroy();
28    });
29  }
30}

Przechowywanie referencji do wszystkich listenerów i ich zbiorowe usuwanie w

destroy()
to najlepsza praktyka przy budowaniu komponentów - szczególnie ważna w dynamicznych aplikacjach, gdzie komponenty są często tworzone i niszczone.

Przejdź do CodeWorlds