Kurs JavaScript i TypeScript · Moduł 8: Zdarzenia i DOM
Obiekt Event i jego właściwości
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 2stopImmediatePropagation() - 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 logPraktyczny 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ł wydarzenieevent.currentTarget- element z listeneremevent.type- typ wydarzeniaevent.clientX/Y,event.pageX/Y- pozycje kursoraevent.ctrlKey,event.shiftKey,event.altKey- klawisze modyfikująceevent.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. Która właściwość obiektu event zawiera element, który wywołał wydarzenie?
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: