Kurs JavaScript i TypeScript · Moduł 8: Zdarzenia i DOM

Obiekt Event i jego właściwości

9 min czytania
W tej lekcji8

"Każdy raport z sensorów w parku," Ray Arnold otwiera szczegółową dokumentację systemu, "zawiera precyzyjne informacje: gdzie, kiedy, jakiego typu wydarzenie, z jaką intensywnością. Podobnie obiekt Event w JavaScript to kompletny raport o tym, co się wydarzyło w przeglądarce."

Ray wskazuje na monitor pokazujący dane z czujnika ruchu przy zagrodzie T-Rexa. "Widzisz? Czas: 14:23:47, lokalizacja: sektor B-7, typ ruchu: szybki, kierunek: północny. Każda informacja jest kluczowa dla właściwej reakcji."

Czym jest obiekt Event?

Każde wydarzenie w JavaScript automatycznie tworzy obiekt Event, który jest przekazywany do funkcji obsługującej jako pierwszy parametr. Ten obiekt zawiera wszystkie istotne informacje o wydarzeniu - od pozycji kursora, przez wciśnięte klawisze, aż po element, który wywołał wydarzenie.

Podstawowa anatomia obiektu Event

"Zacznijmy od fundamentów," mówi Ray, otwierając panel diagnostyczny.

1const parkMap = document.getElementById('interactive-map');
2
3parkMap.addEventListener('click', function(event) {
4  // event to obiekt zawierający wszystkie informacje
5
6  console.log('=== RAPORT WYDARZENIA ===');
7  console.log('Typ wydarzenia:', event.type); // 'click'
8  console.log('Timestamp:', event.timeStamp); // czas w ms od załadowania strony
9  console.log('Event phase:', event.eventPhase); // 1=capture, 2=target, 3=bubble
10
11  // Informacje o pozycji kliknięcia
12  console.log(`Kliknięto na pozycji: X=${event.clientX}, Y=${event.clientY}`);
13
14  // Element, który został kliknięty
15  console.log('Kliknięty element:', event.target);
16  console.log('Tag name:', event.target.tagName);
17  console.log('ID elementu:', event.target.id);
18
19  // Element, do którego przypisany jest event listener
20  console.log('Element z listenerem:', event.currentTarget);
21
22  // Czy można anulować domyślną akcję?
23  console.log('Cancelable:', event.cancelable);
24
25  // Czy wydarzenie bąbelkuje?
26  console.log('Bubbles:', event.bubbles);
27});

Właściwości pozycji - system współrzędnych

"W Parku Jurajskim," wyjaśnia Ray, "używamy różnych systemów współrzędnych - GPS, lokalne koordynaty sektorów, pozycje względem bram. Podobnie JavaScript oferuje kilka systemów współrzędnych dla pozycji myszy."

Różne typy współrzędnych

1const viewingPlatform = document.getElementById('platform');
2
3viewingPlatform.addEventListener('click', function(event) {
4  console.log('=== SYSTEMY WSPÓŁRZĘDNYCH ===');
5
6  // 1. clientX/Y - pozycja względem okna przeglądarki (viewport)
7  console.log('Viewport:', event.clientX, event.clientY);
8  console.log('To jak pozycja względem Twojego monitora kontrolnego');
9
10  // 2. pageX/Y - pozycja względem całej strony (uwzględnia scroll)
11  console.log('Strona:', event.pageX, event.pageY);
12  console.log('To jak współrzędne na pełnej mapie parku');
13
14  // 3. screenX/Y - pozycja względem całego ekranu
15  console.log('Ekran:', event.screenX, event.screenY);
16  console.log('To jak pozycja GPS - bezwzględna lokalizacja');
17
18  // 4. offsetX/Y - pozycja względem klikniętego elementu
19  console.log('Offset:', event.offsetX, event.offsetY);
20  console.log('To jak pozycja w obrębie konkretnej zagrody');
21
22  // Praktyczne użycie - zaznaczanie obszaru na mapie
23  markLocationOnMap(event.pageX, event.pageY);
24  showCoordinatesInfo(event.clientX, event.clientY);
25});
26
27function markLocationOnMap(x, y) {
28  const marker = document.createElement('div');
29  marker.className = 'location-marker';
30  marker.style.left = x + 'px';
31  marker.style.top = y + 'px';
32  document.body.appendChild(marker);
33
34  console.log(`Zaznaczono lokalizację na mapie: (${x}, ${y})`);
35}
36
37function showCoordinatesInfo(clientX, clientY) {
38  const tooltip = document.getElementById('coordinates-tooltip');
39  tooltip.textContent = `Pozycja: X=${clientX}, Y=${clientY}`;
40  tooltip.style.left = (clientX + 10) + 'px';
41  tooltip.style.top = (clientY + 10) + 'px';
42  tooltip.style.display = 'block';
43}

target vs currentTarget - kluczowa różnica

"To jeden z najczęstszych błędów," ostrzega Ray. "Mylenie target z currentTarget. To jak mylenie dinozaura, który uciekł, z zagrodą, z której uciekł."

Zrozumienie różnicy

1// HTML:
2// <div id="enclosure" class="dino-enclosure">
3//   <div class="info-panel">
4//     <h3>Zagroda T-Rex</h3>
5//     <button class="feed-button">Nakarm</button>
6//     <button class="status-button">Status</button>
7//   </div>
8// </div>
9
10const enclosure = document.getElementById('enclosure');
11
12enclosure.addEventListener('click', function(event) {
13  // target - element, który RZECZYWIŚCIE został kliknięty
14  console.log('Target (kliknięty element):', event.target);
15  console.log('Target tag:', event.target.tagName);
16
17  // currentTarget - element, do którego przypisany jest listener
18  console.log('CurrentTarget (element z listenerem):', event.currentTarget);
19  console.log('CurrentTarget ID:', event.currentTarget.id); // zawsze 'enclosure'
20
21  // Praktyczny przykład
22  if (event.target.classList.contains('feed-button')) {
23    console.log('Kliknięto przycisk karmienia');
24    console.log('W zagrodzie:', event.currentTarget.id);
25    feedDinosaur(event.currentTarget.id);
26  } else if (event.target.classList.contains('status-button')) {
27    console.log('Kliknięto przycisk statusu');
28    console.log('Dla zagrody:', event.currentTarget.id);
29    showEnclosureStatus(event.currentTarget.id);
30  } else {
31    console.log('Kliknięto gdzieś w zagrodzie, ale nie w przycisk');
32  }
33});
34
35function feedDinosaur(enclosureId) {
36  console.log(`Rozpoczynam karmienie w zagrodzie: ${enclosureId}`);
37  console.log('Aktywuję system bezpieczeństwa...');
38  console.log('Podaję pokarm...');
39  console.log('Karmienie zakończone');
40}
41
42function showEnclosureStatus(enclosureId) {
43  console.log(`=== STATUS ZAGRODY ${enclosureId} ===`);
44  console.log('Temperatura: 28°C');
45  console.log('Wilgotność: 65%');
46  console.log('Ostatnie karmienie: 12:30');
47  console.log('Aktywność dinozaura: Normalna');
48}

Klawisze modyfikujące - wielofunkcyjne akcje

"W systemach krytycznych," wyjaśnia Ray, "często potrzebujemy kombinacji klawiszy dla bezpieczeństwa. Jak dwa klucze do arsenału. Obiekt Event pokazuje nam, które klawisze są wciśnięte."

Wykrywanie klawiszy modyfikujących

1const dinosaurCard = document.querySelectorAll('.dinosaur-card');
2
3dinosaurCard.forEach(card => {
4  card.addEventListener('click', function(event) {
5    console.log('=== ANALIZA KLAWISZY ===');
6    console.log('Ctrl wciśnięty:', event.ctrlKey);
7    console.log('Shift wciśnięty:', event.shiftKey);
8    console.log('Alt wciśnięty:', event.altKey);
9    console.log('Meta (Cmd/Win) wciśnięty:', event.metaKey);
10
11    const dinoId = this.dataset.dinoId;
12    const dinoName = this.dataset.dinoName;
13
14    // Różne akcje w zależności od kombinacji klawiszy
15    if (event.ctrlKey && event.shiftKey) {
16      // Ctrl + Shift + Click = Zaawansowane opcje
17      console.log('Otwieranie zaawansowanych opcji dla:', dinoName);
18      openAdvancedOptions(dinoId);
19
20    } else if (event.ctrlKey) {
21      // Ctrl + Click = Dodaj do wielokrotnego wyboru
22      console.log('Dodawanie do selekcji:', dinoName);
23      addToSelection(dinoId);
24      this.classList.toggle('selected');
25
26    } else if (event.shiftKey) {
27      // Shift + Click = Wybierz zakres
28      console.log('Wybór zakresu do:', dinoName);
29      selectRange(dinoId);
30
31    } else if (event.altKey) {
32      // Alt + Click = Szybki podgląd
33      console.log('Szybki podgląd:', dinoName);
34      showQuickPreview(dinoId);
35
36    } else {
37      // Zwykłe kliknięcie
38      console.log('Wybrano:', dinoName);
39      clearSelection();
40      selectSingle(dinoId);
41      this.classList.add('selected');
42    }
43  });
44});
45
46// Implementacja pomocniczych funkcji
47let selectedDinosaurs = [];
48
49function addToSelection(dinoId) {
50  if (!selectedDinosaurs.includes(dinoId)) {
51    selectedDinosaurs.push(dinoId);
52  } else {
53    selectedDinosaurs = selectedDinosaurs.filter(id => id !== dinoId);
54  }
55  console.log('Wybrane dinozaury:', selectedDinosaurs);
56}
57
58function clearSelection() {
59  selectedDinosaurs = [];
60  document.querySelectorAll('.dinosaur-card.selected').forEach(card => {
61    card.classList.remove('selected');
62  });
63  console.log('Wyczyszczono selekcję');
64}
65
66function selectSingle(dinoId) {
67  selectedDinosaurs = [dinoId];
68  console.log('Pojedynczy wybór:', dinoId);
69}
70
71function selectRange(endId) {
72  console.log('Implementacja wyboru zakresu do:', endId);
73  // Logika wyboru zakresu między ostatnim wybranym a aktualnym
74}

Informacje o przycisku myszy

"Różne sensory reagują na różne typy sygnałów," Ray pokazuje panel z trzema przyciskami. "Podobnie możemy reagować różnie na lewy, prawy i środkowy przycisk myszy."

Wykrywanie przycisku myszy

1const interactiveMap = document.getElementById('park-map');
2
3interactiveMap.addEventListener('mousedown', function(event) {
4  console.log('=== INFORMACJE O PRZYCISKU MYSZY ===');
5  console.log('Przycisk:', event.button);
6  console.log('0 = lewy, 1 = środkowy, 2 = prawy');
7  console.log('Buttons (bitmask):', event.buttons);
8
9  const clickX = event.clientX;
10  const clickY = event.clientY;
11
12  switch(event.button) {
13    case 0:
14      // Lewy przycisk - standardowa akcja
15      console.log('Lewy przycisk: Zaznaczanie lokalizacji');
16      selectLocation(clickX, clickY);
17      break;
18
19    case 1:
20      // Środkowy przycisk - nowa karta/okno
21      console.log('Środkowy przycisk: Otwieranie w nowym oknie');
22      event.preventDefault();
23      openInNewWindow(clickX, clickY);
24      break;
25
26    case 2:
27      // Prawy przycisk - menu kontekstowe
28      console.log('Prawy przycisk: Menu kontekstowe');
29      showContextMenu(clickX, clickY);
30      break;
31
32    default:
33      console.log('Nieznany przycisk:', event.button);
34  }
35});
36
37// Blokowanie domyślnego menu kontekstowego
38interactiveMap.addEventListener('contextmenu', function(event) {
39  event.preventDefault();
40  console.log('Zablokowano domyślne menu - używamy własnego');
41});
42
43function selectLocation(x, y) {
44  console.log(`Wybrano lokalizację: (${x}, ${y})`);
45  highlightArea(x, y);
46}
47
48function showContextMenu(x, y) {
49  const menu = document.getElementById('custom-context-menu');
50  menu.style.left = x + 'px';
51  menu.style.top = y + 'px';
52  menu.style.display = 'block';
53
54  console.log('Wyświetlono menu kontekstowe na pozycji:', x, y);
55}

Metody obiektu Event

"Oprócz właściwości," Ray pokazuje następny ekran, "obiekt Event ma też metody - narzędzia do kontrolowania zachowania wydarzenia."

preventDefault() - blokowanie domyślnej akcji

1// Przykład 1: Własna walidacja formularza
2const securityForm = document.getElementById('security-access-form');
3
4securityForm.addEventListener('submit', function(event) {
5  event.preventDefault(); // BLOKUJ domyślne wysłanie formularza
6
7  console.log('Przechwycono wysłanie formularza');
8
9  const formData = new FormData(securityForm);
10  const accessCode = formData.get('access-code');
11  const securityLevel = formData.get('security-level');
12
13  // Własna walidacja
14  if (validateAccessCode(accessCode)) {
15    console.log('Kod dostępu poprawny');
16    grantAccess(securityLevel);
17  } else {
18    console.error('Nieprawidłowy kod dostępu!');
19    showError('Kod dostępu jest nieprawidłowy');
20  }
21});
22
23// Przykład 2: Niestandardowa nawigacja
24const navigationLinks = document.querySelectorAll('a.custom-navigation');
25
26navigationLinks.forEach(link => {
27  link.addEventListener('click', function(event) {
28    event.preventDefault(); // BLOKUJ standardowe przejście
29
30    const targetUrl = this.getAttribute('href');
31    console.log('Przechwycono kliknięcie linku:', targetUrl);
32
33    // Własna logika nawigacji (np. SPA routing)
34    navigateToPage(targetUrl);
35  });
36});
37
38function validateAccessCode(code) {
39  const validCodes = ['ALPHA-7', 'BETA-4', 'GAMMA-9'];
40  return validCodes.includes(code);
41}
42
43function navigateToPage(url) {
44  console.log('Nawigacja do:', url);
45  // Implementacja SPA routing
46  history.pushState({}, '', url);
47  loadPageContent(url);
48}

stopPropagation() - zatrzymywanie propagacji

1// HTML:
2// <div class="park-section" id="section-a">
3//   <div class="enclosure" id="enclosure-1">
4//     <button class="action-button">Akcja</button>
5//   </div>
6// </div>
7
8const parkSection = document.getElementById('section-a');
9const enclosure = document.getElementById('enclosure-1');
10const actionButton = document.querySelector('.action-button');
11
12// Listener na sekcji parku
13parkSection.addEventListener('click', function(event) {
14  console.log('3. Kliknięto sekcję parku');
15  updateSectionStatus();
16});
17
18// Listener na zagrodzie
19enclosure.addEventListener('click', function(event) {
20  console.log('2. Kliknięto zagrodę');
21  event.stopPropagation(); // STOP! Nie propaguj wyżej
22  highlightEnclosure();
23});
24
25// Listener na przycisku
26actionButton.addEventListener('click', function(event) {
27  console.log('1. Kliknięto przycisk akcji');
28  performAction();
29  // Wydarzenie będzie propagować do enclosure, ale tam zostanie zatrzymane
30});
31
32// Bez stopPropagation() w enclosure - zobaczylibyśmy wszystkie 3 logi
33// Z stopPropagation() - zobaczymy tylko 1 i 2

stopImmediatePropagation() - całkowite zatrzymanie

1const emergencyButton = document.getElementById('emergency');
2
3// Pierwszy listener
4emergencyButton.addEventListener('click', function(event) {
5  console.log('1. Pierwszy listener - aktywacja alarmu');
6  activateAlarm();
7
8  event.stopImmediatePropagation(); // STOP WSZYSTKO!
9});
10
11// Drugi listener - NIE wykona się!
12emergencyButton.addEventListener('click', function(event) {
13  console.log('2. Ten listener się NIE wykona');
14  closeGates();
15});
16
17// Trzeci listener - NIE wykona się!
18emergencyButton.addEventListener('click', function(event) {
19  console.log('3. Ten też się NIE wykona');
20  notifyStaff();
21});
22
23// Po kliknięciu zobaczymy tylko pierwszy log

Praktyczny system monitoringu

"Połączmy wszystko w działający system," mówi Ray, otwierając główny kod.

1class DinosaurMonitoringSystem {
2  constructor() {
3    this.selectedDinosaurs = [];
4    this.monitoringActive = false;
5    this.initialize();
6  }
7
8  initialize() {
9    console.log('Inicjalizacja systemu monitoringu dinozaurów');
10    this.setupEventListeners();
11  }
12
13  setupEventListeners() {
14    const dinosaurCards = document.querySelectorAll('.dinosaur-card');
15
16    dinosaurCards.forEach(card => {
17      // Click event z pełną analizą
18      card.addEventListener('click', (event) => {
19        this.handleDinosaurClick(event);
20      });
21
22      // Context menu
23      card.addEventListener('contextmenu', (event) => {
24        event.preventDefault();
25        this.showDinosaurMenu(event);
26      });
27
28      // Hover effects
29      card.addEventListener('mouseenter', (event) => {
30        this.showQuickInfo(event);
31      });
32
33      card.addEventListener('mouseleave', (event) => {
34        this.hideQuickInfo(event);
35      });
36    });
37  }
38
39  handleDinosaurClick(event) {
40    const card = event.currentTarget;
41    const dinoId = card.dataset.dinoId;
42    const dinoName = card.dataset.dinoName;
43
44    console.log('=== RAPORT KLIKNIĘCIA ===');
45    console.log('Dinozaur:', dinoName);
46    console.log('Pozycja kliknięcia:', event.clientX, event.clientY);
47    console.log('Timestamp:', event.timeStamp);
48    console.log('Ctrl:', event.ctrlKey);
49    console.log('Shift:', event.shiftKey);
50    console.log('Alt:', event.altKey);
51    console.log('Przycisk myszy:', event.button);
52
53    // Logika selekcji
54    if (event.ctrlKey) {
55      this.toggleSelection(dinoId, card);
56    } else if (event.shiftKey) {
57      this.selectRange(dinoId);
58    } else {
59      this.selectSingle(dinoId, card);
60    }
61  }
62
63  toggleSelection(dinoId, card) {
64    const index = this.selectedDinosaurs.indexOf(dinoId);
65
66    if (index === -1) {
67      this.selectedDinosaurs.push(dinoId);
68      card.classList.add('selected');
69      console.log(`Dodano ${dinoId} do selekcji`);
70    } else {
71      this.selectedDinosaurs.splice(index, 1);
72      card.classList.remove('selected');
73      console.log(`Usunięto ${dinoId} z selekcji`);
74    }
75
76    console.log('Wybrane dinozaury:', this.selectedDinosaurs);
77  }
78
79  selectSingle(dinoId, card) {
80    // Wyczyść poprzednią selekcję
81    document.querySelectorAll('.dinosaur-card.selected').forEach(c => {
82      c.classList.remove('selected');
83    });
84
85    this.selectedDinosaurs = [dinoId];
86    card.classList.add('selected');
87
88    console.log('Wybrano pojedynczego dinozaura:', dinoId);
89  }
90
91  showDinosaurMenu(event) {
92    const card = event.currentTarget;
93    const dinoId = card.dataset.dinoId;
94
95    console.log('Wyświetlanie menu kontekstowego dla:', dinoId);
96    console.log('Pozycja:', event.clientX, event.clientY);
97
98    // Tutaj byłaby logika wyświetlania menu
99  }
100
101  showQuickInfo(event) {
102    const card = event.currentTarget;
103    const dinoName = card.dataset.dinoName;
104
105    console.log(`Najechano na: ${dinoName}`);
106    console.log('Pozycja myszy:', event.clientX, event.clientY);
107  }
108
109  hideQuickInfo(event) {
110    console.log('Opuszczono kartę dinozaura');
111  }
112}
113
114// Inicjalizacja systemu
115const monitoringSystem = new DinosaurMonitoringSystem();

Podsumowanie

"Obiekt Event," podsumowuje Ray, "to Twoje okno do świata interakcji użytkownika. Każda właściwość, każda metoda ma swoje znaczenie i zastosowanie. Podobnie jak każdy odczyt z sensora w Parku Jurajskim pomaga nam utrzymać bezpieczeństwo, tak właściwości Event pomagają Ci tworzyć precyzyjne, responsywne aplikacje."

Kluczowe właściwości:

  • event.target - element, który wywołał wydarzenie
  • event.currentTarget - element z listenerem
  • event.type - typ wydarzenia
  • event.clientX/Y, event.pageX/Y - pozycje kursora
  • event.ctrlKey, event.shiftKey, event.altKey - klawisze modyfikujące
  • event.button - przycisk myszy

Kluczowe metody:

  • event.preventDefault() - blokuj domyślną akcję
  • event.stopPropagation() - zatrzymaj propagację
  • event.stopImmediatePropagation() - zatrzymaj wszystkie listenery
Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Rejestracja Odwiedzających - Park Jurajski</title>
7    <style>
8        body {
9            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10            background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
11            margin: 0;
12            padding: 20px;
13            color: white;
14        }
15
16        .registration-container {
17            max-width: 500px;
18            margin: 0 auto;
19            background: rgba(0, 0, 0, 0.8);
20            border-radius: 15px;
21            padding: 30px;
22            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
23        }
24
25        h1 {
26            text-align: center;
27            margin-bottom: 30px;
28            color: #ecf0f1;
29        }
30
31        .form-group {
32            margin-bottom: 20px;
33        }
34
35        label {
36            display: block;
37            margin-bottom: 5px;
38            color: #bdc3c7;
39            font-weight: 500;
40        }
41
42        input[type="text"],
43        input[type="email"],
44        input[type="number"],
45        input[type="date"],
46        select {
47            width: 100%;
48            padding: 12px;
49            border: 2px solid #34495e;
50            border-radius: 8px;
51            background: rgba(255, 255, 255, 0.1);
52            color: white;
53            font-size: 16px;
54            transition: all 0.3s ease;
55            box-sizing: border-box;
56        }
57
58        input[type="text"]:focus,
59        input[type="email"]:focus,
60        input[type="number"]:focus,
61        input[type="date"]:focus,
62        select:focus {
63            outline: none;
64            border-color: #3498db;
65            background: rgba(255, 255, 255, 0.15);
66        }
67
68        .input-valid {
69            border-color: #27ae60 !important;
70            background: rgba(39, 174, 96, 0.1) !important;
71        }
72
73        .input-invalid {
74            border-color: #e74c3c !important;
75            background: rgba(231, 76, 60, 0.1) !important;
76        }
77
78        .error-message {
79            color: #e74c3c;
80            font-size: 14px;
81            margin-top: 5px;
82            display: none;
83        }
84
85        .submit-btn {
86            width: 100%;
87            padding: 15px;
88            background: linear-gradient(45deg, #27ae60, #2ecc71);
89            color: white;
90            border: none;
91            border-radius: 8px;
92            font-size: 18px;
93            font-weight: bold;
94            cursor: pointer;
95            transition: all 0.3s ease;
96            margin-top: 20px;
97        }
98
99        .submit-btn:hover {
100            transform: translateY(-2px);
101            box-shadow: 0 10px 25px rgba(39, 174, 96, 0.3);
102        }
103
104        .submit-btn:disabled {
105            background: #95a5a6;
106            cursor: not-allowed;
107            transform: none;
108            box-shadow: none;
109        }
110
111        .success-message {
112            background: #27ae60;
113            color: white;
114            padding: 15px;
115            border-radius: 8px;
116            margin-top: 20px;
117            text-align: center;
118            display: none;
119        }
120
121        .form-info {
122            background: rgba(52, 152, 219, 0.2);
123            border: 1px solid #3498db;
124            border-radius: 8px;
125            padding: 15px;
126            margin-bottom: 20px;
127        }
128
129        .required {
130            color: #e74c3c;
131        }
132    </style>
133</head>
134<body>
135    <div class="registration-container">
136        <h1>Rejestracja Odwiedzających</h1>
137
138        <div class="form-info">
139            Wypełnij formularz, aby zarejestrować się na wycieczkę po Parku Jurajskim.
140            Pola oznaczone <span class="required">*</span> są wymagane.
141        </div>
142
143        <form id="visitor-form">
144            <div class="form-group">
145                <label for="name">Imię i nazwisko <span class="required">*</span></label>
146                <input type="text" id="name" name="name" required>
147                <div class="error-message" id="name-error">Imię i nazwisko musi mieć co najmniej 2 znaki</div>
148            </div>
149
150            <div class="form-group">
151                <label for="email">Adres email <span class="required">*</span></label>
152                <input type="email" id="email" name="email" required>
153                <div class="error-message" id="email-error">Wprowadź poprawny adres email</div>
154            </div>
155
156            <div class="form-group">
157                <label for="age">Wiek <span class="required">*</span></label>
158                <input type="number" id="age" name="age" min="5" max="120" required>
159                <div class="error-message" id="age-error">Wiek musi być między 5 a 120 lat</div>
160            </div>
161
162            <div class="form-group">
163                <label for="phone">Numer telefonu</label>
164                <input type="text" id="phone" name="phone" placeholder="+48 123 456 789">
165                <div class="error-message" id="phone-error">Wprowadź poprawny numer telefonu</div>
166            </div>
167
168            <div class="form-group">
169                <label for="visit-date">Data wizyty <span class="required">*</span></label>
170                <input type="date" id="visit-date" name="visit-date" required>
171                <div class="error-message" id="date-error">Data wizyty musi być w przyszłości</div>
172            </div>
173
174            <div class="form-group">
175                <label for="tour-type">Typ wycieczki <span class="required">*</span></label>
176                <select id="tour-type" name="tour-type" required>
177                    <option value="">Wybierz typ wycieczki</option>
178                    <option value="standard">Standardowa (2h)</option>
179                    <option value="vip">VIP z przewodnikiem (4h)</option>
180                    <option value="night">Nocna wycieczka (3h)</option>
181                    <option value="research">Wycieczka badawcza (6h)</option>
182                </select>
183                <div class="error-message" id="tour-error">Wybierz typ wycieczki</div>
184            </div>
185
186            <button type="submit" id="submit-btn" class="submit-btn" disabled>
187                Zarejestruj się na wycieczkę
188            </button>
189        </form>
190
191        <div id="success-message" class="success-message">
192            Rejestracja zakończona pomyślnie! Potwierdzenie zostało wysłane na podany adres email.
193        </div>
194    </div>
195
196    <script>
197        // Pobieramy referencje do elementów formularza
198        const form = document.getElementById('visitor-form');
199        const submitBtn = document.getElementById('submit-btn');
200        const successMessage = document.getElementById('success-message');
201
202        // Funkcje walidacji
203        function validateName(name) {
204            return name.length >= 2;
205        }
206
207        function validateEmail(email) {
208            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
209            return emailRegex.test(email);
210        }
211
212        function validateAge(age) {
213            const ageNum = parseInt(age);
214            return ageNum >= 5 && ageNum <= 120;
215        }
216
217        function validatePhone(phone) {
218            if (phone === '') return true; // Pole opcjonalne
219            const phoneRegex = /^(\+\d{1,3}\s?)?\d{3}\s?\d{3}\s?\d{3}$/;
220            return phoneRegex.test(phone.replace(/\s/g, ''));
221        }
222
223        function validateDate(date) {
224            const selectedDate = new Date(date);
225            const today = new Date();
226            today.setHours(0, 0, 0, 0);
227            return selectedDate > today;
228        }
229
230        function validateTourType(tourType) {
231            return tourType !== '';
232        }
233
234        // Funkcja do pokazywania/ukrywania błędów
235        function showError(fieldId, show) {
236            const errorElement = document.getElementById(fieldId + '-error');
237            const inputElement = document.getElementById({ date: 'visit-date', tour: 'tour-type' }[fieldId] || fieldId);
238
239            if (show) {
240                errorElement.style.display = 'block';
241                inputElement.classList.add('input-invalid');
242                inputElement.classList.remove('input-valid');
243            } else {
244                errorElement.style.display = 'none';
245                inputElement.classList.remove('input-invalid');
246                inputElement.classList.add('input-valid');
247            }
248        }
249
250        // Funkcja sprawdzająca czy formularz jest poprawny
251        function checkFormValidity() {
252            const name = document.getElementById('name').value;
253            const email = document.getElementById('email').value;
254            const age = document.getElementById('age').value;
255            const phone = document.getElementById('phone').value;
256            const visitDate = document.getElementById('visit-date').value;
257            const tourType = document.getElementById('tour-type').value;
258
259            const isValid = validateName(name) &&
260                           validateEmail(email) &&
261                           validateAge(age) &&
262                           validatePhone(phone) &&
263                           validateDate(visitDate) &&
264                           validateTourType(tourType);
265
266            submitBtn.disabled = !isValid;
267            return isValid;
268        }
269
270        // Event listenery dla walidacji w czasie rzeczywistym
271        document.getElementById('name').addEventListener('input', function(e) {
272            const isValid = validateName(e.target.value);
273            showError('name', !isValid);
274            checkFormValidity();
275        });
276
277        document.getElementById('email').addEventListener('input', function(e) {
278            const isValid = validateEmail(e.target.value);
279            showError('email', !isValid);
280            checkFormValidity();
281        });
282
283        document.getElementById('age').addEventListener('input', function(e) {
284            const isValid = validateAge(e.target.value);
285            showError('age', !isValid);
286            checkFormValidity();
287        });
288
289        document.getElementById('phone').addEventListener('input', function(e) {
290            const isValid = validatePhone(e.target.value);
291            showError('phone', !isValid);
292            checkFormValidity();
293        });
294
295        document.getElementById('visit-date').addEventListener('input', function(e) {
296            const isValid = validateDate(e.target.value);
297            showError('date', !isValid);
298            checkFormValidity();
299        });
300
301        document.getElementById('tour-type').addEventListener('change', function(e) {
302            const isValid = validateTourType(e.target.value);
303            showError('tour', !isValid);
304            checkFormValidity();
305        });
306
307        // Obsługa wysłania formularza
308        form.addEventListener('submit', function(e) {
309            e.preventDefault(); // Zatrzymujemy standardowe wysłanie formularza
310
311            // Jeszcze raz sprawdzamy poprawność
312            if (!checkFormValidity()) {
313                alert('Formularz zawiera błędy. Popraw je przed wysłaniem.');
314                return;
315            }
316
317            // Zbieramy dane z formularza
318            const formData = new FormData(form);
319            const visitorData = {
320                name: formData.get('name'),
321                email: formData.get('email'),
322                age: parseInt(formData.get('age')),
323                phone: formData.get('phone') || 'Nie podano',
324                visitDate: formData.get('visit-date'),
325                tourType: formData.get('tour-type')
326            };
327
328            console.log('Dane odwiedzającego:', visitorData);
329
330            // Symulujemy wysłanie danych
331            submitBtn.textContent = 'Rejestruję...';
332            submitBtn.disabled = true;
333
334            setTimeout(() => {
335                // Ukrywamy formularz i pokazujemy komunikat sukcesu
336                form.style.display = 'none';
337                successMessage.style.display = 'block';
338
339                console.log('Rejestracja zakończona pomyślnie!');
340
341                // Po 3 sekundach pokazujemy ponownie formularz
342                setTimeout(() => {
343                    form.style.display = 'block';
344                    successMessage.style.display = 'none';
345                    form.reset();
346                    submitBtn.textContent = 'Zarejestruj się na wycieczkę';
347                    checkFormValidity();
348
349                    // Resetujemy style pól
350                    document.querySelectorAll('input, select').forEach(input => {
351                        input.classList.remove('input-valid', 'input-invalid');
352                    });
353
354                    document.querySelectorAll('.error-message').forEach(error => {
355                        error.style.display = 'none';
356                    });
357                }, 3000);
358            }, 2000);
359        });
360
361        // Ustawiamy minimalną datę na jutro
362        const tomorrow = new Date();
363        tomorrow.setDate(tomorrow.getDate() + 1);
364        document.getElementById('visit-date').min = tomorrow.toISOString().split('T')[0];
365    </script>
366</body>
367</html>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Która właściwość obiektu event zawiera element, który wywołał wydarzenie?

  2. 2. Do czego służy metoda event.preventDefault()?

Zadania praktyczne w grze

  • Edytor kodu

    W pliku index.html jest formularz rejestracji gości (pola name i email) i skrypt w znaczniku <script>. Uzupełnij luki: ___BLANK1___ to zdarzenie wysłania formularza, ___BLANK2___ to metoda obiektu event, która zatrzymuje domyślne wysłanie i przeładowanie strony, a ___BLANK3___ to warunek, który jest prawdziwy, gdy e-mail nie zawiera znaku @. Poprawne dane mają wyświetlić powitanie z imieniem, błędne komunikat o błędzie.

  • Układanie w poziomie

    Ułóż elementy zapobiegania domyślnej akcji w event handlerze:

Przydatne artykuły