Jakiś czas temu zrobiłem swój własny event handler dla wszystkich niecodziennych zdarzeń (czyli innych niż domyślne, DOMowe), a że z równania „dawno nie pisałem” + „data ostatniego postu” wychodzi mi „wstyd” – chciałbym opisać proces tworzenia takiego skryptu.
Na wstępnie chciałbym zaznaczyć, że nie chcę zaprzestawać pisania bloga, ale z uwagi, że mam ograniczony wolny czas, postanowiłem pisać notki w maksimum 45 minut plus ewentualna korekta. Wierzę, że okaże się to solidnym antidotum na brak nowych postów. Tak więc – zaczynamy.
Pierwsze, co należy sobie uzmysłowić przy pisaniu event handlera to sposób jego działania. To jest relatywnie proste. Przyjmijmy, że mamy zdarzenie o nazwie item changed, które wskazuje, że coś tam nam się zmieniło w aplikacji – na przykład właściwość innerHTML jakiegoś elementu DOM. Analogicznie do zdarzeń typu onclick, mouseover i tak dalej – przypisujemy zdarzeniu item changed jakąś funkcję, która się odpali po każdym wystąpieniu… item changed. Z kolei to swoiste wystąpienie zdarzenia definiujemy za pomocą funkcji, takich jak fireEvent, fire, triggerEvent czy jakiejkolwiek, która będzie nam się podobać.
Pseudokod wygląda następująco:
BibliotekaOdEventow.przypiszJakiesZdarzenieFunkci(
nazwaZdarzenia,
funkcjaKtoraMaSieOdpalicPoWykryciuZdarzenia,
kontekstWJakimUruchomionaBedzieFunkcja);
W innej części aplikacji, jeśli zdarzenie wystąpiło, dajemy o tym znać części odpowiedzialnej za zdarzenia:
BibliotekaOdEventow.wywolajZdarzenie(nazwaZdarzenia);
W ten sposób funkcja, którą przypisaliśmy do zdarzenia o danej nazwie automatycznie uruchomi się po użyciu pseudo-metody wywolajZdarzenie.
System ten działa podobnie do standardowych zdarzeń DOM:
element.addEventListener("click", function() {}, false);
Tyle że w naszym wypadku musimy dać znać, że chcemy wywołać jakieś zdarzenie. W DOM naturalnie robi to za nas przeglądarka i silnik JS. Do dzieła!
Definiujemy najpierw obiekt – bibliotekę, która będzie odpowiedzialna za trzy zadania – dodanie zdarzenia, uruchomienie go i usunięcie.
var EventHandler = {
_events: {}
};
Trochę pustawo – mamy jedynie jedną składową obiektu, którą jest _events. To w tym podobiekcie, robiącym za tablicę asocjacyjną, będziemy przetrzymywać wszystkie dodane zdarzenia. No właśnie, musimy je jakoś dodawać. Niech służy temu metoda bind:
var EventHandler = {
_events: {},
bind: function(name, callback, context) {
if (arguments.length === 1 && typeof arguments[0] === "object") {
var eventName = arguments[0].name;
var callback = arguments[0].callback;
var context = arguments[0].context;
}
var eventName = eventName || name;
if (!eventName || !callback) {
throw new Error("Name and callback are required!")
}
if (!(typeof eventName == "string")) {
throw new Error("Event name should be a string!");
}
if (!(typeof callback == "function")) {
throw new Error("Event callback should be a function!");
}
if (!eventName.replace(/\s/, "")) {
throw new Error("Event name should not be an empty string!");
}
if (!this._events[eventName]) {
this._events[eventName] = [];
}
this._events[name].push((!arguments[1]) ? arguments[0] : {name: eventName, callback: callback, context: context});
},
Dzięki takiej, a nie innej konstrukcji funkcji bind, możemy jej użyć dwojako:
EventHandler.bind("item changed", function() { alert("Item is changed"); }, this);
EventHandler.bind({name: "item changed", callback: function() { alert("Item is changed"); }, context: this});
Co ciekawego robi funkcja bind? Najpierw sprawdza, czy argumentem jest obiekt, jeśli tak to przypisuje jego właściwości do zmiennych. Potem mamy serię sprawdzeń, czy podane parametry funkcji są właściwe. Jeśli nie – plujemy stosownym błędem, wbudowanym w silnik JS. Na końcu dodajemy do tablicy w _events nowy obiekt, reprezentujący zdarzenie.
Czas na metodę fire:
fire: function(name, data) {
if (name && this._events[name]) {
var data = data || {};
for (var i = this._events[name].length-1; i > -1; i--) {
var res = this._events[name][i].callback.call(this._events[name][i].context, data);
if (res === false) {
return false;
}
}
}
},
Sprawdza ona, czy zdarzenie o podanej nazwie name zostało dodane i uruchamia wszystkie callbacki co do tego zdarzenia w podanym kontekście (context). Opcjonalnie możemy uruchomić fire z drugim parametrem. Wtedy zostanie on przekazany do każdego callbacka. Co ciekawe, jeśli nasza funkcja zwraca false – zatrzymuje uruchamianie pozostałych callbacków. Zdarzenia uruchamiane są bąbelkowo.
Usuwanie jest trywialne:
remove: function(name) {
if (this._events[name]) {
delete this._events[name];
}
}
Jak to działa w praktyce? Na przykład tak:
var Controller = function() {
this.itemValue = "value";
this.setValue = function(value) {
this.itemValue = value;
EventHandler.fire("item changed", { newValue: value });
};
};
var View = function() {
this.alert = function(eventData) {
alert("Nowa wartość to: " + eventData.newValue);
};
EventHandler.bind("item changed", this.alert, this);
};
var c = new Controller;
var v = new View;
c.setValue("new value");
Warto dodać, że podobną funkcjonalność oferuje jQuery – możemy w łatwy sposób podpinać swoje zdarzenia, a „wyzwalać je” (co u nas robi funkcja fire) poprzez trigger.

Komentarze
Bedą kolejne części jquery?
Teksty o jQuery nie stanowia dla mnie wyzwania – nawet nie wiem, co mialbym napisac. Jesli padna propozycje tematow to sie zastanowie :-)
@matiit
Ludzie, co wy byscie zrobili jakby John nie napisal jQuery? ;-)
JS to taki (calkiem) prosty i fajny jezyk…
Bardzo fajny art – warto wiedziec jak zrobic takie rzeczy „od kuchni”.
Prototype robi dokladnie to samo uzywajac wrapperow: Event.observe(‘twoj:event’) by ‘zlapac’ event, oraz funkcji .fire(‘twoj:event’) ktora go wywoluje. Bardzo przydatne przy skryptach ktore ladowane sa zbyt pozno (np. po document.ready/dom:loaded)
a moze cos o json, ajax w jquery? lub plugin?