Zdarzenia#

Architektura#

W specyfikacji D3E umieszczono specjalny rozdział, w którym znajdziemy teoretyczny opis architektury zdarzeń DOM. Z programistycznego punktu widzenia cały proces przetwarzania zdarzeń obrazują algorytmy wysłania i wywołania, które zdefiniowano w najnowszym DOM4. Moim zdaniem rzetelna analizach tych dwóch algorytmów mogłaby zastąpić większość przytaczanych tutaj opisów teoretycznych (warto spróbować).

Wysyłanie zdarzeń i przepływ zdarzeń#

Aplikacje mogą wysyłać # (dispatch) obiekty zdarzeń przy użyciu polecenia EventTarget.dispatchEvent(), i w takiej sytuacji implementacje muszą wysłać takie zdarzenia poprzez tę metodę. Zachowanie metody zależy od mechanizmu przepływu zdarzeń # (event flow) powiązanego z pierwotnym obiektem. Przepływ zdarzeń opisuje jak obiekty zdarzeń propagują # (propagate) poprzez strukturę danych, czyli w jaki sposób się rozchodzą.

Standardy W3C próbują uporządkować złożoną sytuację braku kompatybilności, w której dwie najbardziej popularne przeglądarki z przeszłości zaoferowały kompletnie różne modele przepływu zdarzeń:

  1. Przechwycenie zdarzenia podczas jego propagacji z poziomu okna do obiektu docelowego. Jest to klasyczny model z przeglądarki Netscape.
  2. Odpowiadanie na zdarzenia w trakcie ich propagacji od obiektu docelowego do obiektów zewnętrznych (tzw. bąbelkowanie). Jest to klasyczny model z przeglądarki IE, który tym razem okazał się lepszym podejściem, dlatego też w większości przypadków stosuje się wyłącznie bąbelkowanie.

Ustandaryzowany mechanizm W3C łączący dwa odmienne modele przepływu zdarzeń można wyrazić za pomocą poniższej grafiki.

Wysyłanie zdarzenia w drzewie węzłów przy użyciu przepływu zdarzeń DOM

Rysunek. Wysyłanie zdarzenia w drzewie węzłów przy użyciu przepływu zdarzeń DOM

Obiekty zdarzeń są wysyłane do celu zdarzenia # (event target). Na początku wysyłania implementacje muszą ustalić ścieżkę propagacji # (propagation path) obiektu zdarzenia.

Ścieżką propagacji jest uporządkowana lista aktualnych celów zdarzeń # (current event targets), przez które musi przejść obiekt zdarzenia. W przypadku implementacji DOM ścieżka zdarzenia musi odzwierciedlać hierarchiczną strukturę drzewa węzłów danego dokumentu. Ostatnią pozycją w liście musi być cel zdarzenia; poprzedzające pozycje na liście są przodkami celu (target's ancestors), a bezpośrednio poprzedzające pozycje są rodzicami celu (target's parent). Po określeniu ścieżki propagacji nie może ona ulec zmianie; dla implementacji DOM dotyczy to nawet sytuacji, kiedy element w ścieżce propagacji jest przesuwany lub usuwany z drzewa DOM.

Dla przykładu, w przepływie zdarzeń DOM uchwyty zdarzeń mogą zmienić pozycję celu zdarzenia w dokumencie, w czasie gdy obiekt zdarzenia został już wysłany; zmiany te nie wpływają na ścieżkę propagacji, która ustalana jest przed wysłaniem zdarzenia.

Wyjątki generowane wewnątrz uchwytów zdarzeń nie mogą zatrzymać propagacji zdarzenia lub wpływać na ścieżkę propagacji. Wyjątki we własnej postaci nie rozprzestrzeniają się na zewnątrz zasięgu uchwytu zdarzenia.

W poniższym przykładzie zrzucenie wyjątku poprzez wywołanie throw "Error" nie propaguje do zasięgu rodzica, co uniemożliwiałoby wykonanie metody alert():

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	var e = document.createEvent("Event");
	e.initEvent("myevent", false, false);
	var target = document.createElement("div");

	target.addEventListener("myevent", function() {
		throw "Error";
	});

	target.dispatchEvent(e); // nasłuch zdarzenia zrzuca wyjątek

	// Wcześniejsze zrzucenie wyjątku nie ma wpływu na poniższy kod
	alert("Wyjątek? Nie ma problemu");

</script>

W następnym kroku obiekt zdarzenia musi zakończyć jedną lub większą ilość faz zdarzeń # (event phases). Specyfikacja zdarzeń definiuje trzy fazy zdarzeń:

  1. Faza przechwytywania # (capture phase) - obiekt zdarzenia musi propagować poprzez przodków celu z widoku domyślnego aż do rodzica celu. Uchwyty zdarzeń zarejestrowane dla tej fazy muszą przechwycić/obsłużyć zdarzenie przed osiągnięciem swojego celu.
  2. Faza celu # (target phase) - obiekt zdarzenia musi osiągnąć cel zdarzenia. Uchwyty zdarzeń zarejestrowane dla tej fazy muszą przechwycić/obsłużyć zdarzenie w chwili osiągnięcia swojego celu. Jeśli typ zdarzenia wskazuje, że zdarzenie nie bąbelkuje, to obiekt zdarzenia musi się zatrzymać po zakończeniu tej fazy.
  3. Faza bąbelkowania # (bubble phase) - obiekt zdarzenia musi propagować poprzez przodków celu w odwróconej kolejności, zaczynając od rodzica celu i kończąc na widoku domyślnym. Uchwyty zdarzeń zarejestrowane dla tej fazy muszą przechwycić/obsłużyć zdarzenie po osiągnięciu swojego celu.

Obiekty zdarzeń wykonują te fazy w podanej kolejności przy użyciu cząstkowych ścieżek propagacji (partial propagation paths), czyli trzech ścieżek podanych wyżej. Faza musi zostać pominięta jeśli nie jest obsługiwana, lub kiedy propagacja obiektu zdarzenia zostaje zatrzymana. Dla przykładu, jeśli właściwość Event.bubbles ustawiona zostanie na boolowską wartość false, to faza bąbelkowania zostanie pominięta, oraz kiedy metoda Event.stopPropagation() zostanie wywołana przed wysłaniem zdarzenia, to wszystkie fazy muszą zostać pominięte.

Implementacje muszą pozwolić obiektom zdarzeń zrealizować fazę zdarzenia stosując następujące kroki, w czasie kiedy są przemieszczane do oczekujących celów zdarzeń w cząstkowej ścieżce propagacji dla tej fazy i propagacja obiektu zdarzenia nie została zatrzymana za pomocą Event.stopPropagation().

Po pierwsze implementacja musi ustalić aktualny cel # (current target). Musi on być kolejnym oczekującym celem zdarzenia w cząstkowej ścieżce propagacji, zaczynając od pierwszego. Aktualny cel będzie udostępniany przez właściwość Event.currentTarget.

Następnie implementacja musi określić kandydujące uchwyty zdarzeń # (candidate event listeners) dla aktualnego celu. Musi to być lista wszystkich uchwytów zdarzeń, które zostały zarejestrowane na aktualnym celu, zgodnie z kolejnością ich rejestracji. HTML5 określa kolejność uchwytów zarejestrowanych poprzez atrybutowe uchwyty zdarzeń. Kandydujące uchwyty zdarzeń po określeniu nie mogą być zmieniane; dodawanie lub usuwanie uchwytów nie ma wpływu na kandydujące uchwyty zdarzeń aktualnego celu.

W ostatnim kroku implementacja musi przetworzyć wszystkie kandydujące uchwyty zdarzeń w odpowiedniej kolejności i wywołać poszczególne procedury obsługi, jeśli wszystkie poniższe warunki są spełnione:

W czasie przetwarzania ścieżki propagacji, jeśli widok domyślny (np. obiekt window) implementuje interfejs EventTarget, to zdarzenie propaguje od widoku domyślnego do obiektu document w fazie przechwytywania, i od obiektu document do widoku domyślnego w fazie bąbelkowania.

Po tym jak zdarzenie wypełni wszystkie fazy zdarzenia na jego ścieżce propagacji, to jego właściwość Event.currentTarget musi zostać ustawiona na wartość null i właściwość Event.eventPhase musi zostać ustawiona na 0. Wszystkie inne właściwości z interfejsu Event (lub interfejsów dziedziczących po nim) pozostają niezmienione (w tym właściwość Event.target, która musi nadal wskazywać na cel zdarzenia).

Model zdefiniowany powyżej musi być stosowany niezależnie od specyficznego przepływu zdarzeń skojarzonego z celem zdarzenia. Każdy przepływ zdarzeń musi definiować, w jaki sposób ścieżka propagacji ma być określana i które fazy zdarzeń są obsługiwane. Przepływ zdarzeń DOM (DOM event flow) jest zastosowaniem tego modelu: ścieżka propagacji dla obiektu implementującego interfejs Node musi zostać określona poprzez jego łańcuch Node.parentNode, i jeśli ma zastosowanie dla dokumentu zawierającego widok domyślny, to wszystkie zdarzenia wykonują fazy przechwytywania i celu, a wykonanie fazy bąbelkowania musi zostać zdefiniowana indywidualnie dla każdego typu zdarzenia. Alternatywne zastosowania tego modelu można znaleźć w specyfikacji "DOM3 Load nad Save".

Implementacje modelu zdarzeń DOM muszą być wielobieżne. Uchwyty zdarzeń mogą wykonywać czynności, które powodują wysyłanie dodatkowych zdarzeń. Takie zdarzenia są obsługiwane w sposób synchroniczny; propagacja zdarzenia, która spowodowała wyzwolenie uchwytu zdarzenia musi być wznawiana dopiero po zakończeniu wysłania nowego zdarzenia:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<script>

		// Uruchom po całkowitym załadowaniu dokumentu
		window.onload = function(){

			var button = document.getElementById("press");
			var info = document.getElementById("info");

			var test = function(){

				info.innerHTML += "Drugi uchwyt dla przycisku (nowe zdarzenie piesek)." + "<br><br>";

			};

			button.addEventListener("click", function(e){

				info.innerHTML += "Pierwszy uchwyt dla przycisku (zdarzenie click)." + "<br><br>";

				// Nowe zdarzenie zostanie obsłużone przed zakończeniem obsługi aktualnego zdarzenia
				var event = document.createEvent("Event");
				event.initEvent("piesek", true, true);
				button.dispatchEvent(event);

				info.innerHTML += "Wciąż pierwszy uchwyt dla przycisku (zdarzenie click)." + "<br><br>";


			}, true);

			button.addEventListener("piesek", test, false);

		}

	</script>

</head>

<body>

	<p>Kliknij w przycisk by wywołać procedury obsługi wielu uchwytów zdarzeń.</p>
	<input id="press" type="button" value="Kliknij mnie!" >

	<p style="color: blue;">Szczegółowe informacje:</p>
	<p id="info"></p>

</body>

</html>

W powyższym przykładzie zamiast wymyślonego zdarzenia typu piesek mógłbym utworzyć i wysłać zdarzenie typu click. Zmiana ta spowodowałaby, że każde kolejne wygenerowane zdarzenie wywoływałoby pierwszą procedurę obsługi (rejestrowaną właśnie dla zdarzenia typu click) i w efekcie doprowadziłoby do zapętlenia (zawieszenia) programu. Przeglądarki są uodpornione na tego typu sytuacje, w razie konieczności przerywają działanie kodu i informują o zaistniałej sytuacji stosownym komunikatem w konsoli:

Przykład#

Upraszczając sprawę można powiedzieć, że obiekt zdarzenia rozpoczyna swoją podróż w dół drzewa węzłów od obiektu globalnego window (faza przechwytywania) i trwa ona aż do chwili osiągnięcia celu zdarzenia (faza celu). Następnie zdarzenie odbija się i bąbelkuje w górę struktury drzewa aż osiągnie ponownie obiekt window. Możemy wskazać, w którym momencie podróży zdarzenie ma zostać przechwycone.

Jeśli obsługa zdarzenia jest ustawiana w momencie schodzenia w dół drzewa, to nie zostanie ona powtórzona podczas podróży zdarzenia z przeciwną stroną. I odwrotnie, kiedy obsługa zdarzenia jest ustawiana w momencie bąbelkowania w górę drzewa, to nie zostanie ona wykonana podczas podróży zdarzenia w przeciwną stronę.

Załóżmy, że mamy trzy pozagnieżdżane pojemniki DIV i do każdego z nich podepniemy uchwyty zdarzeń obsługujące różne fazy dla zdarzenia kliknięcia myszy. Niech najbardziej wysuniętym przodkiem obejmującym tego drzewa węzłów będzie pierwszy z tych kontenerów, czyli DIV1. Całość można wyrazić następującą grfiką:

Przykład propagacji obiektu zdarzenia w przypadku pozagnieżdżanych elementów

Rysunek. Przykład propagacji obiektu zdarzenia w przypadku pozagnieżdżanych elementów

Klikając na element DIV1 uruchomimy tylko jedną procedurę obsługi zdarzenia, która przypisana została do elementu DIV1, aczkolwiek będzie to od razu faza celu (uchwyt bąbelkujący).

Klikając na element DIV2 uruchomimy procedury obsługi zdarzeń w następującej kolejności: DIV2 (cel - z propagacji) -> DIV1 (bąbelkowanie).

Klikając na element DIV3 uruchomimy procedury obsługi zdarzeń w następującej kolejności: DIV2 (propagacja) -> DIV3 (cel - z propagacji) -> DIV3 (cel - z bąbelkowania) -> DIV1 (bąbelkowanie).

Należy zauważyć, że przy podpinaniu uchwytu zdarzenia do obiektu mamy możliwość wybrania jedynie fazy propagacji lub fazy bąbelkowania. Faza celu będzie osiągana automatycznie dla obu tych ustawień, kiedy obiekt zdarzenia przywędruje do celu zdarzenia. Taki stan oznaczyłem po prostu zapisem (cel - z propagacji) lub (cel - z bąbelkowania).

#Nawiązując jeszcze do powyższej sytuacji, kiedy rejestrujemy nasłuch zdarzenia bezpośrednio na celu zdarzenia (czyli obiekcie, który będzie ostatnim aktualnym celem zdarzenia w ścieżce propagacji przy schodzeniu w dół), to z perspektywy kolejności wywoływania procedur obsługi nie ma znaczenia, jaką wartość trzeciego argumentu przekazaliśmy przy rejestrowaniu uchwytu zdarzenia. W takiej sytuacji uchwyty są traktowane identycznie (faza celu) i wykonają się zgodnie z kolejnością rejestracji. Niemniej jednak są to osobne uchwyty, które nie są traktowane jak duplikaty (przy rejestracji) i które w trakcie usuwania wymagają podania identycznych parametrów jak przy rejestracji.

Mechanizm przepływu zdarzeń DOM może z początku wyglądać na bardzo skomplikowany, ale w praktyce okazuje się niezwykle elastyczny i przydatny, szczególnie w przypadku umiejętnego delegowania zdarzeń.

Dobrym uzupełnieniem mogą być eksperymenty z poniższym przykładem praktycznym:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<style>

		#DIV1 {background-color: #34669C; width: 400px; height: 200px;}
		#DIV2 {background-color: green; width: 360px; height: 120px; margin: 20px;}
		#DIV3 {background-color: red; width: 320px; height: 40px; margin: 20px;}

	</style>

	<script>

		// Uruchom po całkowitym załadowaniu dokumentu
		window.onload = function(){

			var div1 = document.getElementById("DIV1");
			var div2 = document.getElementById("DIV2");
			var div3 = document.getElementById("DIV3");
			var info = document.getElementById("info");

			function update(e){

				info.innerHTML += "currentTarget: " + e.currentTarget
					+ "<br>" + "currentTarget.id: " + e.currentTarget.id
					+ "<br>" + "eventPhase: " + e.eventPhase
					+ "<br>" + "target: " + e.target
					+ "<br>" + "target.id: " + e.target.id + "<br><br>";

			}

			div1.addEventListener("click", function(e){

				update(e);

			}, false);

			div2.addEventListener("click", function(e){

				update(e);

			}, true);

			div3.addEventListener("click", function(e){

				update(e);

			}, true);

			div3.addEventListener("click", function(e){

				update(e);

			}, false);

		}

	</script>

</head>

<body>

	<div id="DIV1">DIV1 (bąbelkowanie)

		<div id="DIV2">DIV2 (propagacja)

			<div id="DIV3">DIV3 (propagacja) + DIV3 (bąbelkowanie)</div>

		</div>

	</div>

	<p>Kliknij dowolny kontener by wyzwolić procedury obsługi zdarzeń dla kliknięcia.</p>

	<p style="color: blue;">Szczegółowe informacje dla przechwyconego zdarzenia:</p>
	<p id="info"></p>

</body>

</html>

Domyślne akcje i anulowanie zdarzeń#

Zdarzenia są zwykle wysyłane przez implementację w wyniku działania użytkownika, w odpowiedzi na zakończenie zadania, czy też zasygnalizowania postępu w czasie aktywności asynchronicznej (np. dla żądania sieciowego). Niektóre zdarzenia mogą być wykorzystywane do sterowania zachowaniem, które implementacja może kolejno przyjąć (lub cofnąć akcję, którą implementacja już przyjęła). Zdarzenia z tej kategorii są uznawane za odwoływalne # (cancelable) i zachowanie, które jest anulowane nazywa się domyślną akcją # (default action).

Odwoływalne obiekty zdarzeń mogą być powiązane z jedną lub wieloma domyślnymi akcjami. Aby anulować domyślne akcje dla zdarzenia należy wywołać metodę Event.preventDefault(). Istnieje nieliczna grupa zdarzeń, które nie pozwolą na anulowanie zachowania domyślnego.

Dla przykładu, zdarzenie mousedown jest wysyłane natychmiast po naciśnięciu w dół przycisku na urządzeniu wskazującym (zazwyczaj myszce). Jedną z domyślnych akcji podjętych przez implementację jest ustawienie automatu stanów (state machine), który pozwala użytkownikowi na przeciąganie zdjęć lub zaznaczanie tekstu. Domyślna akcja zależy od tego, co dzieje się dalej - przykładowo, jeśli urządzenie wskazujące użytkownika jest nad tekstem, to zaznaczenie tekstu może zostać rozpoczęte. Jeśli urządzenie wskazujące użytkownika jest nad grafiką, to akcja przeciągania obrazka może się rozpocząć. Anulowanie domyślnej akcji dla zdarzenia mousedown zapobiega występowaniu tych akcji całkowicie.

Domyślne akcje powinny być wykonywane po zakończeniu wysyłania zdarzenia, ale w wyjątkowych okolicznościach mogą być również wykonywane bezpośrednio przed wysłaniem zdarzenia.

Dla przykładu, domyślna akcja związana ze zdarzeniem click na elemencie <input type="checkbox"> przełącza wartość właściwości checked tego elementu przed wysłaniem zdarzenia. Jeśli domyślna akcja zdarzenia click jest anulowana, to wartość zostanie przywrócona do jej poprzedniego stanu dopiero po zakończeniu wysyłania zdarzenia:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<!DOCTYPE html>
<html>

<head>

	<script>

		function run(){

			var input = document.getElementById("check");
			input.checked = true;

			function check(){
				alert("Poza uchwytem: " + input.checked);
			}

			input.addEventListener("click", function(e){

				e.preventDefault();

				alert("Wewnątrz uchwytu: " + input.checked); // false - wartość przeciwna względem początkowej (akcja domyślna się wykonała)

				setTimeout(check, 2000); // true - po zakończeniu procedury obsługi wartość przywrócona do stanu początkowego

			}, false);

		}

	</script>

</head>

<body onload="run()">

	<p>Kliknij w pole przy przełączyć jego stan i przerwać domyślną akcję.</p>
	<input id="check" type="checkbox">

</body>

</html>

Kiedy zdarzenie jest anulowane, to warunkowe domyślne akcje powiązane ze zdarzeniem muszą zostać pominięte (lub jak wspomniano wyżej, jeśli domyślne akcje są wprowadzane przed wysyłaniem zdarzenia, to ich wpływ musi zostać cofnięty). Możliwość anulowania domyślnych akcji zdarzenia musi być wskazywana przez właściwość Event.cancelable. Metoda Event.preventDefault() zatrzymuje wszystkie domyślne akcje powiązane z obiektem zdarzenia. Właściwość Event.defaultPrevented określa, czy domyślne akcje dla zdarzenia zostały już anulowane (np. przez poprzednie uchwyty zdarzeń). Jeśli aplikacja DOM samodzielnie rozpoczyna wysyłanie zdarzenia, to wartość zwracana przez metodę EventTarget.dispatchEvent() informuje, czy domyślna akcja dla obiektu zdarzenia został anulowana.

Wiele implementacji nadaje specjalne znaczenie dla wartości zwracanych przez uchwyty zdarzeń; zwrócenie boolowskiej wartości flase oznacza, że domyślna akcja dla odwoływalnych zdarzeń zostanie anulowana (chociaż uchwyty Window.onerror są przerywane poprzez zwrócenie boolowskiej wartości true).

Niektóre z odwoływalnych zdarzeń mogą nie mieć żadnych dostrzegalnych domyślnych akcji (np. zdarzenie mousemove).

Specyfikacja zdarzeń nie oferuje możliwości programowego zapytania, czy obiekt zdarzenia jest powiązany z domyślną akcją, lub kojarzenia nowych domyślnych akcji z obiektem zdarzenia. Pozostałe specyfikacje powinny określać, które domyślne akcje, jeśli w ogóle występują, są powiązane z niektórymi obiektami zdarzeń. Dodatkowo, implementacje mogą dowiązywać domyślne akcje do zdarzeń, jeśli jest to konieczne i właściwe dla tej specyfikacji.

Przykładowo, jedna z implementacji może przewijać (scroll) widok dokumentu w odpowiedzi na domyślną akcję dla zdarzenia wheel, podczas gdy inna implementacja może powiększać (zoom) dokument w odpowiedzi na domyślną akcję.

Zdarzenia synchroniczne i asynchroniczne#

Zdarzenia mogą być wysyłane synchronicznie lub asynchronicznie.

Zdarzenia synchroniczne # (synchronous events) muszą być traktowane tak, jakby były w wirtualnej kolejce spełniającej model pierwszy wchodzi-pierwszy wychodzi (first-in-first-out), w porządku zgodnym z sekwencją czasową wystąpienia w stosunku do: innych zdarzeń, zmian w drzewie węzłów oraz interakcji użytkownika. Każde zdarzenie w tej wirtualnej kolejce musi być opóźnione do chwili, kiedy poprzednie zdarzenie zakończy propagację, lub zostanie anulowane. Niektóre synchroniczne zdarzenia są dostarczane przez konkretne urządzenia lub proces, tak jak w przypadku zdarzeń przycisków myszy; zdarzenia te są regulowane poprzez algorytmy porządkowe zdarzeń definiowane dla tego zestawu zdarzeń, a aplikacje klienckie muszą wysyłać takie zdarzenia w określonym porządku.

Dla przykładu, kiedy użytkownik podwójnie kliknie na pewnym fragmencie tekstu, aby zaznaczyć słowo, a następnie naciśnie klawisz 'Delete', aby usunąć słowo, to wywoła następującą synchroniczną sekwencję zdarzeń: mousedown, mouseup, click, mousedown, mouseup, dblclick, select i keydown. Każde z tych zdarzeń jest odpalane w sekwencji zapoczątkowanej przez działanie użytkownika.

Zdarzenia asynchroniczne # (synchronous events) mogą być wysyłane w chwili, kiedy rezultaty pozostałych akcji są zakończone, bez względu na: inne zdarzenia, zmiany w drzewie węzłów oraz interakcję użytkownika.

Przykładowo, w czasie ładowania dokumentu, element skryptu wewnętrznego jest parsowany i wykonywany. Zdarzenie load umieszczane w elemencie skryptu jest kolejkowane asynchronicznie. Jednakże, ponieważ jest ono zdarzeniem asynchronicznym, to jego stały porządek w stosunku do innych synchronicznych zdarzeń odpalanych w czasie ładowania dokument (np. zdarzenia DOMContentLoaded z HTML5) nie jest gwarantowany.

Kolejność zdarzeń i pętla zdarzeń#

Większość zdarzeń odbywa się w kontekście sekwencyjnym. HTML5 definiuje swoje operacje zdarzeń w kategoriach mechanizmu pętli zdarzeń # (event loop), gdzie zdarzenia wszystkich typów są wprowadzane do różnych kolejek zadań # (task queques). Specyfikacja zdarzeń nie definiuje zdarzeń w kategoriach mechanizmu pętli zdarzeń, ale jest zgodna z tym mechanizmem. Zamiast tego specyfikacja zdarzeń definiuje kilka operacji w specyficznej kolejności zdarzeń # (event orders).

Używając terminologi z HTML5, każde niezależne urządzenie, takie jak mysz lub klawiatura, należy traktować jako źródło zadań # (task source), które trafiają do odpowiedniej kolejki zadań, w określonym porządku ustalanym przez kolejności zdarzeń powiązanej z tym urządzeniem; każda operacja, jak na przykład zmiana zogniskowania lub kompozycja wejścia, także działa jak źródło zadań dla odpowiadającej kolejki zadań. Przetworzenie tych pętli zdarzeń jest traktowane w sposób zgodny z językiem gospodarza (host language), takim jak HTML5.

Niektóre zdarzenia, jak na przykład skróty (hotkeys) lub klawisze sterujące (control keys) wciśnięte w pewnej sekwencji, mogą zostać przechwycone przez system operacyjny lub aplikację, przerywając w ten sposób oczekiwaną kolejność zdarzeń.

Zaufane zdarzenia#

Zdarzenia generowane przez aplikacje klienckie, będące odpowiedzią na interakcje użytkownika, czy odpowiedzią na zmiany w DOM, są zaufanymi zdarzeniami # (trusted events) dla aplikacji klienckich, z przywilejami, które nie zostały ograniczone dla wygenerowanych zdarzeń przez skrypt metodą Document.createEvent("Event"), zmodyfikowane przy użyciu metody Event.initEvent(), lub wysłane metodą EventTarget.dispatchEvent(). Właściwość Event.isTrusted zaufanych zdarzeń ma boolowską wartość true, kiedy niezaufane zdarzenia # (untrusted events) mają boolowską wartość false.

Większość niezaufanych zdarzeń (zwanych też zdarzeniami syntetycznymi) nie powinna wyzwalać domyślnych akcji, z wyjątkiem zdarzenia click. Zdarzenie to zawsze wyzwala domyślną akcję, nawet jeśli wartością właściwości Event.isTrusted jest boolowskie false (wynika to z chęci zapewnienia wstecznej kompatybilności). Wszystkie inne niezaufane zdarzenia muszą zachowywać się tak, jakby wywołana została metoda Event.preventDefault() na tych zdarzeniach.

Tworzenie niezaufanych zdarzeń#

Programiści mogą samodzielnie generować zdarzenia syntetyczne wprost ze skryptu. Służą do tego dwa sposoby:

Metoda Document.createEvent() #

Pierwotny sposób tworzenia niezaufanych zdarzeń, który po raz pierwszy wprowadzono w specyfikacji "DOM Level 2 Events". Wciąż jest opisywany w niektórych specyfikacjach i obsługiwany przez wszystkie implementacje, ale jego stosowanie jest mniej wygodne, jeśli porównamy go z dedykowanymi konstruktorami.

Metoda tworzy nowe zdarzenie dziedziczące po interfejsie, który został do niej przekazany. W celu zachowania kompatybilności z już utworzonym kodem najnowszy DOM4 zezwala na użycie jedynie kilku łańcuchów znakowych.

Tak utworzonego zdarzenia nie można wysłać dopóki nie zostanie zainicjowane kolejną metodą pokroju init*Event. Problem w tym, że metodzie inicjującej należy przekazać wszystkie parametry charakteryzujące dane zdarzenia, zgodnie z kolejnością podawaną przez specyfikację, co już stanowi spore utrudnienie, szczególnie kiedy wymagane jest podanie kilku parametrów. Jeśli nie zależy nam na zmianie domyślnych wartości we wszystkich właściwościach to drobnym ułatwieniem będzie inicjowanie metodą przewidzianą dla jednego z odziedziczonych interfejsów, która wymaga przekazania mniejszej liczby argumentów, np. UIEvent.initUIEvent() zamiast MouseEvent.initMouseEvent().

W niektórych przypadkach, np. inicjowaniu zdarzeń kółka, samo wywołanie metody WheelEvent.initWheelEvent() może okazać się niewystarczające ponieważ metoda nie obsługuje wszystkich właściwości danego zdarzenia (np. niektórych dziedziczonych z MouseEvent). W związku z tym, jeśli zależy nam na ustawieniu brakujących właściwości, to koniecznym staje się wywoływanie dodatkowych metod inicjujących, które zostały przewidziane dla odziedziczonych interfejsów.

Z uwagi na powyższe wady specyfikacja D3E dla swoich interfejsów zdarzeń osobno umieszcza prawie wszystkie metody inicjujące w dodatku A i w zasadzie uznaje je za przestarzałe. Pozostałe specyfikacje wciąż mogą definiować tego typu rozwiązanie, ale równolegle wprowadzają możliwość wywołania danego interfejsu w roli konstruktora. Metody inicjujące mogą być przydatne jedynie w sytuacji, kiedy zależy nam na ponownym wysłaniu tego samego zdarzenia (czyli konkretnego obiektu JS), ale z innymi cechami (np. blokadą bąbelkowania).

Dedykowany konstruktor #

Konstruktory to najwygodniejszy sposób tworzenia niezaufanych zdarzeń. Zwracane przez nie zdarzenia są inicjowane automatycznie (na zasadzie ustawienia flagi inicjalizacji), przez co wywoływanie dodatkowych metod inicjujących nie jest konieczne. Wszystkie interfejsy z modelu zdarzeń DOM oraz najnowsze interfejsy z innych specyfikacji mogą być wywoływane w roli konstruktorów.

Konstruktor przyjmuje także drugi opcjonalny argument, który w Web IDL reprezentowany jest przez słownik. W większości przypadków już sama definicja słownika określa domyślne wartości dla wszystkie właściwości zdarzenia. Słowniki mogą dziedziczyć po sobie, dzięki czemu ich teoretyczne opisy są krótsze. W ujęciu praktycznym słownik będzie po prostu obiektem JavaScript, w którym wystarczy ustawić tylko te właściwości (włącznie z dziedziczonymi), których wartość ma być inna niż domyślna. Właściwości z domyślnymi wartościami będą przekazywane automatycznie i wymienianie ich w obiekcie, choć możliwe, staje się zbędne. Trzeba podkreślić, że kolejność umieszczania właściwości w obiekcie jest nieistotna.

Prosty przykład:

  1. L
  2. K
  3. T'
  4. T
  5. A
  6. O
  7. Z'
  8. Z
  9. #
<script>

	// Konstruktor
	var event1 = new MouseEvent("mouseover", {
		bubbles: true,
		cancelable: true,
		relatedTarget: document
	});

	// Metoda
	var event2 = document.createEvent("MouseEvent");
	event2.initMouseEvent("mouseover",
			     true,
			     true,
			     window,
			     null,
			     null,
			     null,
			     0,
			     0,
			     null,
			     null,
			     null,
			     null,
			     null,
			     document);

	document.addEventListener("mouseover", function(){

		document.write("Zdarzenie złapane!" + "<br>");

	}, false);

	document.dispatchEvent(event1);
	document.dispatchEvent(event2);

</script>

Całkowite przejście na nowocześniejsze konstruktory z pewnością zajmie sporo czasu. Na chwilę obecną jedynie przeglądarka Chrome pozwala na wywoływanie w roli konstruktorów wszystkich obsługiwanych przez siebie interfejsów zdarzeń. W przypadku Firefoksa mamy do czynienia z pewnymi problemami. Kulą u nogi będzie IE11 z kompletnym brakiem wsparcia dla konstruktorów zdarzeń.

Aktywujące wyzwalacze i zachowania#

Niektóre cele zdarzeń (takie jak odsyłacz lub przycisk) mogą być powiązane z aktywującym zachowaniem (activation behavior), np. podążeniem za linkiem, które musi zostać wykonane przez implementacje w odpowiedzi na aktywującego wyzwalacza # (activation trigger), np. kliknięcie na link.

Język gospodarza powinien wskazać, czy i które elementy mają aktywujące zachowanie, przydzielając im odpowiednie aktywujące wyzwalacze, i określając rezultat dla tego aktywującego zachowania. Implementacje obsługujące język gospodarza powinny inicjować to aktywujące zachowanie, gdy skojarzony z nim aktywujący wyzwalacz występuje.

Dla przykładu, obydwa języki HTML i SVG posiadają element <a>, który wskazuje na odsyłacz. Istotnym aktywującym wyzwalaczem dla elementu <a> jest zdarzenie click na tekście lub obrazie będącym zawartością elementu <a>, czy też zdarzenie keydown będące wynikiem wciśnięcia przycisku 'Enter', kiedy element <a> miał zogniskowanie (fokus). Aktywującym zachowaniem dla elementu <a> jest zazwyczaj zmiana zawartości okna na zawartość nowego dokumentu, w odniesieniu do zewnętrznych linków, lub zmiana położenia bieżącego dokumentu względem nowej kotwicy, w przypadku wewnętrznych linków.

Aktywujący wyzwalacz jest akcją użytkownika lub zdarzeniem, który wskazuje implementacji, że powinna zainicjować aktywujące zachowanie. Aktywujące wyzwalacze inicjowane przez użytkownika to m.in. kliknięcie przycisku myszy na aktywowalnym elemencie, wciśnięcie przycisku 'Enter', kiedy aktywowalny element ma zogniskowanie, czy też wciśnięcie przycisku, który jest w jakiś sposób powiązany z aktywowalnym elementem (np. hotkey lub access key), nawet jeśli element ten nie ma zogniskowania. Aktywujące wyzwalacze bazujące na zdarzeniach mogą obejmować: zdarzenia oparte na zegarze (timer-based), które aktywują element w określonym czasie lub po upływie pewnego czasu, zdarzenia postępu (progress events), gdzie pewne działania zostały zakończone, lub też wiele innych zdarzeń bazujących na warunkach (condition-based) lub stanach (state-based).

W niektórych przypadkach język gospodarza może definiować atrybuty, a nawet przypisywać wartości, które dodają lub zmieniają natywny aktywujący wyzwalacz lub aktywujące zachowanie elementu. Dla przykładu, specyfikacja ARIA określa wartość atrybutu role, który nadaje semantyki elementowi, na którym atrybut jest użyty, w celu polepszenia dostępności. W takich przypadkach, jeśli język gospodarza jednoznacznie nie zdefiniował aktywującego wyzwalacza i aktywującego zachowania, to autor zawartości musi zapewnić mechanikę aktywującego wyzwalacza (np. za pomocą uchwytu zdarzenia) i aktywującego zachowania (np. za pomocą wywołania funkcji ECMAScript) dla elementu, który zastosował ten atrybut lub jego konkretną wartość.

Aktywujące zdarzenia syntetyczne#

Jeśli instancją aktywującego wyzwalacza nie jest zdarzenie z typem click (to jest, kiedy nie jest ono wynikiem pochodzącym z aktywacji przez użytkownika przycisku lub linku przy użyciu myszy, czy innego równoważnego urządzenia wskazującego), to implementacja musi zsyntetyzować i wysłać zdarzenie typu click jako jedną z domyślnych akcji tego aktywującego wyzwalacza. Wartość właściwości Event.target musi zostać ustawiona na cel zdarzenia (zazwyczaj na obecnie zogniskowany element), a zdarzenie musi zasymulować lewe kliknięcie (tj. wartość właściwości MouseEvent.button musi wynosić 0 i wartość MouseEvent.buttons musi wynosić 1). Inne informacje w kontekście tak zasymulowanego zdarzenia click zależą od implementacji, ale ze względów historycznych, interfejsem dla zdarzenia click musi być interfejs MouseEvent, niezależnie od aktualnego urządzenia używanego do aktywacji elementu. Przerwanie domyślnej akcji dla aktywującego wyzwalacza, np. za pomocą metody Event.preventDefault(), musi zatrzymać zainicjowanie aktywującego zachowania.

Dla przykładu, gdy użytkownik aktywuje hiperłącze za pomocą klawiatury, np. poprzez zogniskowanie na elemencie hiperłącza i przyciśnięcie klawisza 'Enter' lub ' ', to zdarzenie click będzie wysyłane jako domyślna akcja odpowiednio dla zdarzenia keydown.

Implementacje muszą wysyłać zsyntetyzowane zdarzenie click, jak opisano powyżej, nawet jeśli normalnie nie wysyłają takiego zdarzenia (np. gdy aktywacja jest odpowiedzią na polecenie głosowe, ponieważ specyfikacja zdarzeń nie odnosi się do rodzajów zdarzeń dla urządzeń głosowych).

Aktywująca kolejność zdarzeń#

Aktywujące wyzwalacze i zachowania mogą być definiowane w częściach przez zdarzenia, które są wysyłane w ustalonej kolejności względem siebie. Oto typowa sekwencja zdarzeń dla elementu aktywowanego przez urządzenie wskazujące (tylko istotne zdarzenia są wymienione):

Nazwa zdarzeniaUwagi
1.click
2.DOMActivateDomyślna akcja jeśli wspierana przez aplikację kliencką; zsyntetyzowana; właściwość Event.isTrusted="true".
Wszystkie inne domyślne akcje, w tym aktywujące zachowania

Poniżej znajduje się typowa sekwencja zdarzeń dla zogniskowanego elementu aktywowanego przez zdarzenie klawiatury (tylko istotne zdarzenia są wymienione):

Nazwa zdarzeniaUwagi
1.keydownMusi być klawiszem aktywującym element, takim jak klawisz 'Enter' lub ' ', w przeciwnym razie element nie jest aktywowany
2.clickDomyślna akcja; zsyntetyzowana; właściwość Event.isTrusted="true".
3.DOMActivateDomyślna akcja jeśli wspierana przez aplikację kliencką; zsyntetyzowana; właściwość Event.isTrusted="true".
Wszystkie inne domyślne akcje, w tym aktywujące zachowania

Zdarzenie DOMActivate jest uznawane za przestarzałe, dlatego nie będą go opisywał w szczegółach, chociaż od czasu do czasu może wystąpić w kursie. W zamian należy stosować zdarzenie click, które spełnia analogiczną rolę i ma szersze wsparcie przez różne implementacje.

Wyjątki zdarzeń#

Operacje zdarzeń mogą zrzucać wyjątki zdarzeń (event exceptions) w postaci obiektów implementujących interfejs DOMException.

Specyfikacja D3E (oraz najnowszy DOM4) definiuje następujące wyjątki dla zdarzeń:

TypOpis
"InvalidStateError" #Zrzucany w chwili, kiedy właściwość Event.type nie została określona poprzez zainicjowanie zdarzenia przed wywołaniem metody EventTarget.dispatchEvent(). Dotyczy także sytuacji, kiedy obiekt zdarzenia przekazany do metody EventTarget.dispatchEvent() jest aktualnie wysyłany.
"NotSupportedError" #Zrzucany w chwili, kiedy metodzie DocumentEvent.createEvent() przekazany zostanie interfejs zdarzenia, którego implementacja nie obsługuje.
Pasek społecznościowy

SPIS TREŚCI AKTUALNEJ STRONY

Zdarzenia (H1) Architektura (H2) Wysyłanie zdarzeń i przepływ zdarzeń (H3) Przykład (H4) Domyślne akcje i anulowanie zdarzeń (H3) Zdarzenia synchroniczne i asynchroniczne (H3) Kolejność zdarzeń i pętla zdarzeń (H4) Zaufane zdarzenia (H3) Tworzenie niezaufanych zdarzeń (H3) Aktywujące wyzwalacze i zachowania (H3) Aktywujące zdarzenia syntetyczne (H4) Aktywująca kolejność zdarzeń (H4) Wyjątki zdarzeń (H3)