"Posłuchaj uważnie," mówi Dr. Alan Grant, pochylając się nad konsolą systemu monitoringu. "Dinozaury komunikują się ze sobą za pomocą dźwięków - od niskich pomruków po przerażliwe ryki. W naszym parku potrzebujemy podobnego systemu komunikacji między modułami. I tu właśnie wkraczają Custom Events!"
Do tej pory poznaliśmy wbudowane zdarzenia DOM -
click, keydown, submit czy scroll. Ale JavaScript pozwala nam tworzyć własne, niestandardowe zdarzenia. Dzięki nim możemy budować systemy komunikacji między różnymi częściami aplikacji, podobnie jak system alarmowy w Parku Jurajskim wysyła powiadomienia do różnych stref.Custom Events to zdarzenia definiowane przez programistę, które mogą przenosić dowolne dane i być nasłuchiwane tak samo jak zwykłe zdarzenia DOM.
Aby stworzyć własne zdarzenie, używamy konstruktora
CustomEvent:1// Podstawowe tworzenie Custom Event
2const alertEvent = new CustomEvent('dinoAlert');
3
4// Custom Event z dodatkowymi danymi (detail)
5const escapeEvent = new CustomEvent('dinoEscape', {
6 detail: {
7 dinosaur: 'Velociraptor',
8 zone: 'B',
9 severity: 'critical'
10 }
11});
12
13// Dostęp do danych zdarzenia
14console.log(escapeEvent.detail.dinosaur); // "Velociraptor"
15console.log(escapeEvent.detail.severity); // "critical"
16console.log(escapeEvent.type); // "dinoEscape"Obiekt
detail to specjalne pole Custom Events, w którym możemy przekazać dowolne dane. Jest to jedyny sposób na dołączenie informacji do niestandardowego zdarzenia.Konstruktor
CustomEvent przyjmuje drugi argument - obiekt opcji:1const event = new CustomEvent('systemUpdate', {
2 detail: { module: 'security', status: 'active' }, // Dane zdarzenia
3 bubbles: true, // Czy zdarzenie propaguje w górę DOM
4 cancelable: true, // Czy można wywołać preventDefault()
5 composed: false // Czy przechodzi przez Shadow DOM
6});detail - obiekt z danymi, które chcemy przekazaćbubbles - domyślnie false; ustaw na true, jeśli zdarzenie ma propagować w górę drzewa DOM (bubbling)cancelable - domyślnie false; ustaw na true, jeśli chcesz móc anulować zdarzenie przez preventDefault()Sam obiekt zdarzenia nic nie robi - musimy go wysłać (dispatch) na konkretnym elemencie DOM:
1// Tworzymy element, który będzie emitował zdarzenia
2const controlPanel = document.getElementById('controlPanel');
3
4// Tworzymy zdarzenie
5const fenceAlert = new CustomEvent('fenceBreak', {
6 detail: {
7 zone: 'A',
8 voltage: 0,
9 dinosaur: 'T-Rex'
10 },
11 bubbles: true
12});
13
14// Wysyłamy zdarzenie na elemencie
15controlPanel.dispatchEvent(fenceAlert);Metoda
dispatchEvent() jest dostępna na każdym elemencie DOM. Po jej wywołaniu, zdarzenie zostaje natychmiast wysłane i wszystkie nasłuchujące handlery są wykonywane synchronicznie.Custom Events nasłuchujemy dokładnie tak samo jak zwykłe zdarzenia - przez
addEventListener:1const controlPanel = document.getElementById('controlPanel');
2
3// Rejestrujemy listener dla naszego zdarzenia
4controlPanel.addEventListener('fenceBreak', function(event) {
5 const { zone, voltage, dinosaur } = event.detail;
6 console.log('ALARM! Ogrodzenie strefy ' + zone + ' uszkodzone!');
7 console.log('Dinozaur: ' + dinosaur);
8 console.log('Napięcie: ' + voltage + 'V');
9});
10
11// Inny moduł też może nasłuchiwać tego samego zdarzenia
12controlPanel.addEventListener('fenceBreak', function(event) {
13 if (event.detail.dinosaur === 'T-Rex') {
14 console.log('EWAKUACJA NATYCHMIAST!');
15 }
16});
17
18// Wysyłamy zdarzenie - oba listenery zostaną wywołane
19controlPanel.dispatchEvent(new CustomEvent('fenceBreak', {
20 detail: { zone: 'A', voltage: 0, dinosaur: 'T-Rex' }
21}));Zobaczmy kompletny przykład systemu komunikacji między modułami:
1// System monitoringu Parku Jurajskiego
2const parkSystem = document.getElementById('parkSystem');
3
4// Moduł bezpieczeństwa nasłuchuje alarmów
5parkSystem.addEventListener('securityAlert', function(event) {
6 const { type, zone, message } = event.detail;
7 const alertDiv = document.createElement('div');
8 alertDiv.className = 'alert alert-' + type;
9 alertDiv.textContent = '[' + zone + '] ' + message;
10 document.getElementById('alerts').appendChild(alertDiv);
11});
12
13// Moduł monitoringu dinozaurów nasłuchuje statusów
14parkSystem.addEventListener('dinoStatus', function(event) {
15 const { name, heartRate, location } = event.detail;
16 console.log(name + ': HR=' + heartRate + ', lokacja=' + location);
17});
18
19// Funkcja emitująca alert bezpieczeństwa
20function emitSecurityAlert(type, zone, message) {
21 parkSystem.dispatchEvent(new CustomEvent('securityAlert', {
22 detail: { type, zone, message },
23 bubbles: true
24 }));
25}
26
27// Funkcja emitująca status dinozaura
28function emitDinoStatus(name, heartRate, location) {
29 parkSystem.dispatchEvent(new CustomEvent('dinoStatus', {
30 detail: { name, heartRate, location },
31 bubbles: true
32 }));
33}
34
35// Użycie
36emitSecurityAlert('warning', 'Zone B', 'Raptor aktywność podwyższona');
37emitSecurityAlert('critical', 'Zone A', 'Ogrodzenie uszkodzone!');
38emitDinoStatus('T-Rex', 85, 'Wybieg główny');Domyślnie Custom Events nie propagują w górę drzewa DOM. Aby włączyć bubbling, musisz ustawić
bubbles: true:1// BEZ bubbles (domyślnie) - listener na rodzicu NIE złapie zdarzenia
2const child = document.getElementById('zoneA');
3const parent = document.getElementById('parkSystem');
4
5child.dispatchEvent(new CustomEvent('alert', {
6 detail: { msg: 'test' }
7 // bubbles domyślnie false
8}));
9
10parent.addEventListener('alert', (e) => {
11 // Ten handler NIE zostanie wywołany!
12 console.log('Parent caught:', e.detail);
13});
14
15// Z bubbles: true - zdarzenie propaguje do rodzica
16child.dispatchEvent(new CustomEvent('alert', {
17 detail: { msg: 'test' },
18 bubbles: true // Teraz zdarzenie będzie propagować!
19}));
20
21parent.addEventListener('alert', (e) => {
22 // Ten handler ZOSTANIE wywołany!
23 console.log('Parent caught:', e.detail.msg); // "test"
24});To jest szczególnie przydatne, gdy chcemy nasłuchiwać zdarzeń z wielu dzieci na jednym rodzicu (event delegation dla Custom Events).
Jeśli ustawisz
cancelable: true, możesz anulować zdarzenie:1const event = new CustomEvent('transferDino', {
2 detail: { dinosaur: 'T-Rex', from: 'A', to: 'B' },
3 cancelable: true,
4 bubbles: true
5});
6
7// Listener może anulować zdarzenie
8parkSystem.addEventListener('transferDino', function(e) {
9 if (e.detail.dinosaur === 'T-Rex') {
10 e.preventDefault(); // Anuluj transfer T-Rexa!
11 console.log('Transfer T-Rexa zablokowany ze względów bezpieczeństwa!');
12 }
13});
14
15// Sprawdzamy, czy zdarzenie zostało anulowane
16const wasNotCancelled = parkSystem.dispatchEvent(event);
17console.log('Transfer dozwolony:', wasNotCancelled); // false (bo preventDefault)Metoda
dispatchEvent() zwraca false, jeśli zdarzenie zostało anulowane przez preventDefault(). Dzięki temu emitujący może sprawdzić, czy akcja powinna być kontynuowana.Custom Events świetnie sprawdzają się w wielu scenariuszach:
1// Przykład: decoupling z Custom Events
2// Moduł koszyka nie wie nic o module powiadomień
3const cart = [];
4
5function addToCart(product) {
6 cart.push(product);
7
8 // Emituje zdarzenie - ktokolwiek chce, może nasłuchiwać
9 document.dispatchEvent(new CustomEvent('cartUpdated', {
10 detail: { product, totalItems: cart.length },
11 bubbles: true
12 }));
13}
14
15// Moduł powiadomień nasłuchuje niezależnie
16document.addEventListener('cartUpdated', (e) => {
17 console.log('Dodano: ' + e.detail.product.name);
18});
19
20// Moduł badge'a koszyka też nasłuchuje niezależnie
21document.addEventListener('cartUpdated', (e) => {
22 console.log('Koszyk: ' + e.detail.totalItems + ' elementów');
23});Custom Events to potężne narzędzie do budowania luźno powiązanych (loosely coupled) systemów w JavaScript:
new CustomEvent(name, options) - tworzy niestandardowe zdarzenieelement.dispatchEvent(event) - wysyła zdarzenie na elemenciedetail - obiekt z danymi przekazywanymi w zdarzeniububbles: true - włącza propagację zdarzenia w górę DOMcancelable: true - pozwala anulować zdarzenie przez preventDefault()addEventListener"W Parku Jurajskim," podsumowuje Dr. Grant, "każdy system musi komunikować się z innymi. Custom Events to nasz wewnętrzny system radiowy - każdy moduł może nadawać i każdy może odbierać. Bez tego cały park pogrążyłby się w chaosie."