Pokročilé zpracování událostí napříč platformami

6. února 2003

Standardní model událostí, definovaný ve specifikaci DOM level 2, není v současnosti podporován nejrozšířenějším prohlížečem, Internet Explorerem od Microsoftu. Ten používá vlastní model, který je ale se standardním v mnoha ohledech srovnatelný. Proto si dnes definujeme API, pomocí jehož metod a vlastností budeme moci pracovat s událostmi ve všech moderních prohlížečích.

Základní informace o událostech

Události jsou základem aplikací využívajících grafické uživatelské prostředí, což je i případ webových aplikací. Proto se bez nich neobejdeme a je důležité mít k dispozici prostředky, pomocí nichž lze s nimi pohodlně a rychle pracovat. Právě vytvoření těchto prostředků, tedy jednotného API, se dnes budeme věnovat.

Základní model událostí, standardizovaný v HTML 4.01, podporují správně všechny moderní prohlížeče. V tomto modelu definujeme ovladače událostí pomocí atributů elementů HTML či odpovídajících vlastností v JavaScriptu. Tento model má však dvě nevýhody – není zde možné získat nějakým standardním způsobem doplňující informace o události (například na které tlačítko myši bylo klepnuto) a také se dá vždy jednomu elementu přiřadit jen jeden ovladač dané události. Pro pokročilé aplikace je tedy tento model nevhodný.

Standardní model událostí je již trochu složitější. Na Intervalu se jím už nějaký ten článek zabýval, proto jenom stručně. Tento model podporují prohlížeče Mozilla/Netscape 6+ a Opera 7. Událost v tomto modelu nejprve protéká od nejnižších vrstev dokumentu k danému elementu (této fázi se říká „zachytávání“) a poté zpět („probublávání“), přičemž ji na této cestě mohou zachytit jakékoliv posluchače událostí. Tyto posluchače událostí definujeme pomocí metody addEventListener() elementů dokumentu, které jako argumenty předáváme typ události, funkci, která se má vykonat, a boolean informaci o tom, jestli má posluchač zachycovat událost na jejím protékání vzhůru (true) nebo zpět (false). Dané funkci je potom jako první argument předáván objekt Event s doplňujícími informacemi o události. Pokud chceme posluchač odstranit, použijeme metodu removeEventListener() se stejnými argumenty. (Protékání událostí se netýká pokročilých HTML událostí – focus, blur, load a unload. Tyto události volají posluchače pouze na prvku, kde vznikly.)

Internet Explorer pro Windows dodnes (verze 6) neimplementoval tento standardní model, ale od verze 5 používá svůj vlastní. Základní odlišností je způsob protékání událostí. IE nezná protékání od nejnižších vrstev dokumentu, událost zde začíná na prvku, kde vznikla, a probublává k nejvyšším prvkům dokumentu. Registrace a odregistrace posluchačů probíhá pomocí metod attachEvent() a detachEvent(), které očekávají stejné argumenty jako standardní metody, pouze zde chybí třetí argument (můžeme to chápat tak, že je v IE automaticky nastaven na hodnotu false).

Objekt Event je zde také předáván jako argument dané funkci, ale jedná se pouze odkaz na nestandardní objekt window.event, který používá IE již od verze 4. Tento objekt obsahuje podobné informace jako standardní Event.

Začínáme

Pro metody a vlastnosti našeho jednotného API si nejprve vytvoříme objekt, který je bude zastřešovat a zároveň zajistí, že nám tyto vlastnosti nebudou narušovat globální jmenný prostor:

var events = new Object();

Registrace posluchače

Nyní nadefinujeme první metodu, a sice events.addListener(), která nám bude sloužit k registraci posluchačů. Její kód je trochu delší, proto ho budeme probírat postupně, v závěru si budete moci stáhnout kompletní zdrojové kódy:

events.addListener = function(element,type,fcn)
{

Tato metoda očekává tři argumenty: odkaz na element, kterému se má přiřadit posluchač, typ události (bez „on“, tedy například click) a funkci, která se má provést. Té bude předáván jednotný objekt Event. Všechny posluchače v našem modelu budou činné jenom v probublávací fázi, protože zachytávací fázi nezná IE a bylo by příliš složité (ne-li zbytečné) ji simulovat. Můžeme pokračovat:

if (!element.addEventListener && !element.attachEvent) return false;

Pokud prohlížeč nezná ani jednu z metod, které budeme používat, vrátíme false a ukončíme tak funkci.

if (element.addEventListener) element.addEventListener(type,fcn,false);

Pokud prohlížeč ovládá standardní model událostí, použijeme ho. Jinak musíme pokračovat dál:

  else if (element.attachEvent)
  {
    element.attachEvent(‚on‘ + type,function(event) {
    var objEvent = new Object();
    …
    fcn(objEvent);
    })
  }

Jak vidíte, pokud prohlížeč zná model událostí IE, registrujeme posluchač pomocí metody attachEvent(). U prvního argumentu musíme přidat prefix „on“, protože ho tato metoda očekává. Samotná funkce potom přebírá jako argument objekt Event IE, s jehož pomocí vytvoříme v těle funkce vlastní objekt Event, který bude mít stejné vlastnosti a metody jako ten standardní. Pak ho předáme funkci posluchače, která tak bude mít k dispozici jednotný objekt s doplňujícími informacemi o události.

Na místě, kde bychom měli vytvořit svůj objekt Event, jsem zatím nechal tečky. Nyní se právě jeho vytvoření budeme věnovat:

objEvent.type = event.type;

První vlastností je typ události. Tato vlastnost se jmenuje v obou modelech stejně, proto jenom jednoduše přiřadíme její hodnotu.

objEvent.target = event.srcElement;

Další vlastností je target, tedy odkaz na element, kde událost vznikla. Ten je v objektu Event IE uložen ve vlastnosti srcElement.

objEvent.currentTarget = element;

Vlastnost currentTarget obsahuje odkaz na element, jehož posluchač se právě zpracovává. Sem přiřadíme jednoduše element, kterému posluchač právě přiřazujeme.

objEvent.eventPhase = (objEvent.target == objEvent.currentTarget ? 2 : 3);

Ve vlastnosti eventPhase je uložena fáze události – „1“ pro zachytávání, „2“ pokud je ve svém cílovém elementu a „3“ pro probublávání. První fáze není v našem modelu možná a o zbývajích dvou rozhodneme snadno – pokud se cílový element události shoduje s elementem, jehož posluchač se právě zpracovává, potom je událost ve svém cílovém elementu, jinak se jedná o probublávání.

objEvent.button = (event.button == 2 || event.button == 6 ? 2 : event.button == 4 ? 1 : event.button == 0 ? null : 0);

Vlastnost button obsahuje číslo tlačítka myši, které bylo stisknuto nebo uvolněno. V objektu Event IE je „1“ pro levé, „2“ pro pravé a „4“ pro prostřední, navíc se při stisku více tlačítek hodnoty sčítají. Naproti tomu ve standardním modelu je „0“ pro levé, „1“ pro prostřední a „2“ pro pravé. Proto tyto hodnoty „překládáme“ do standardního modelu, přičemž se při stisku více tlačítek přikláníme k tlačítkům v pořadí levé, pravé, prostřední.

objEvent.altKey = event.altKey;
objEvent.ctrlKey = event.ctrlKey;
objEvent.shiftKey = event.shiftKey;
objEvent.metaKey = false;

Tyto čtyři vlastnosti nesou boolean informaci, zda byla při události stisknuta tlačítka Alt, Ctrl, Shift a Meta. Kromě metaKey obsahuje Event IE všechny vlastnosti se stejnými názvy. Tlačítko Meta se ale pod Windows nevyskytuje, a proto nemohlo být ani stisknuto, takže můžeme přiřadit false.

objEvent.clientX = event.clientX;
objEvent.clientY = event.clientY;

Tyto v obou modelech identické vlastnosti obsahují informace o souřadnicích kurzoru myši vzhlem k oknu prohlížeče.

objEvent.relatedTarget = (objEvent.type == ‚mouseover‘ ? event.fromElement : objEvent.type == ‚mouseout‘ ? event.toElement : null);

Ve vlastnosti relatedTarget se má nacházet odkaz na element, který souvisí s cílovým elementem události. Využívá se v souvislosti s událostmi přechodu myši (mouseover a mouseout), přičemž u události mouseover se jedná o element, který myš opustila, a u události mouseout je to element, do kterého myš vstoupila. Objekt Event IE obsahuje podobné vlastnosti fromElement a toElement, které podle potřeby přiřadíme.

objEvent.stopPropagation = function() { event.cancelBubble = true; };

Ve standardním modelu událostí slouží metoda stopPropagation() k zastavení události, resp. jejího zachycování či probublávání. V objektu Event IE máme k tomuto účelu vlastnost cancelBubble, kterou musíme nastavit na true.

objEvent.preventDefault = function() { event.returnValue = (objEvent.type == ‚mouseover‘ && (objEvent.target.tagName.toLowerCase() == ‚a‘ || objEvent.target.tagName.toLowerCase() == ‚area‘) ? true : false); };

Metoda preventDefault() slouží k zamezení prohlížeči v provedení výchozí akce spojené s danou událostí, například u události submit je to odeslání formuláře. V objektu Event IE musíme v tomto případě nastavit vlastnost returnValue na false, jediná výjimka se týká odkazů a obrázkových map ve spojení s událostí mouseover, kde musíme tuto vlastnost nastavit na true. (Jestli někdo ví o příčině této anomálie, budu rád když se ozve v diskusi, samotného by mě to zajímalo.)

objEvent.AT_TARGET = 2;
objEvent.BUBBLING_PHASE = 3;

Nyní definujeme dvě konstanty, které obsahují čísla jednotlivých fází pro vlastnost eventPhase. Můžeme je poté používat k určení fáze, pokud si nechceme pamatovat čísla. Správně by sem patřila ještě konstanta pro fázi zachycování, tu ale v našem modelu nepotřebujeme.

Ze standardních vlastností a metod objektu Event jsme právě dokázali adekvátně nasimulovat všechny kromě timeStamp, bubbles, cancelable, view, detail, screenX a screenY. Tyto vlastnosti nemají alternativy v objektu Event IE a nenapadá mě ani způsob, jak by šly nasimulovat. I tak si ale myslím, že se s naším objektem již dá vcelku pohodlně pracovat, mnoho z chybějících vlastností navíc většinou vůbec neupotřebíme.

Objekt Event IE obsahuje navíc pár vlastností, které jsou sice nestandardní, ale i tak mohou být užitečné. Proto jsem se rozhodl „přibalit“ je do našeho objektu Event také, před jejich použitím v cílové funkci byste se ale měli přesvědčit o jejich existenci.

objEvent.keyCode = event.keyCode;
objEvent.charCode = event.keyCode;

Události klávesnice nebyly prozatím standardizovány, přesto IE a Opera 7 používají vlastnost keyCode objektu Event pro uložení pozice v Unicode znaku, který je výsledkem stisknutí klávesy. Mozilla používá vlastnost keyCode také, ale ta se zde chová jinak. Obdobnou funkci jako keyCode u IE a Opery zde má vlastnost charCode. Proto byste se nejprve měli dotázat na existenci vlastnosti charCode a až v případě její neexistence použít hodnotu keyCode.

objEvent.offsetX = event.offsetX;
objEvent.offsetY = event.offsetY;
objEvent.x = event.x;
objEvent.y = event.y;

Toto jsou vlastnosti typické pro Event IE. První dvě určují pozici kurzoru vzhledem k cílovému elementu a druhé dvě určují souřadnice místa, na němž došlo k události, vzhledem k nejbližšímu elementu s vlastností position jinou než static.

Nyní tedy máme nadefinován objekt Event i přiřazen posluchač události a můžeme funkci events.addListener() ukončit:

  }
  return true;
};

Odstranění posluchače

Dále si definujeme také metodu removeListener(), která bude posluchače odstraňovat. Ta je krátká a nepotřebuje již dalšího komentáře:

events.removeListener = function(element,type,fcn)
{
  if (!element.removeEventListener && !element.detachEvent) return false;
  if (element.removeEventListener) element.removeEventListener(type,fcn,false);
  else if (element.detachEvent) element.detachEvent(‚on‘ + type,fcn);
  return true;
};

Automatická registrace posluchačů

Nakonec si definujeme ještě funkci, která pracuje s polem objektů, kde jsou uloženy informace o posluchačích událostí, a přiřazuje tyto posluchače příslušným elementům. Může probíhat i při nahrávání stránky a plně nám tak nahradí atributové ovladače událostí, které již tím pádem nemusí znepřehledňovat kód naší stránky. Funkce bude pracovat s polem events.listenersArray:

events.listenersArray = new Array();

Každá položka pole je objekt, který obsahuje tři vlastnosti: element, jehož hodnotou je řetězec, který když vyhodnotíme funkcí eval(), dostaneme odkaz na element; dále type jehož hodnotou je typ události; a fcn, jehož hodnotou je odkaz na funkci posluchače.

Nyní již k samotné funkci:

events.addListeners = function()
{
  var i, element, repeat = false;
  for (i = 0; i < events.listenersArray.length; i++)
  {
    element = (events.listenersArray[i].element != “ ? eval(events.listenersArray[i].element) : false);
    if (element)
    {
      events.addListeners.able = events.addListener(element,events.listenersArray[i].type,events.listenersArray[i].fcn);
      events.listenersArray[i].element = “;
    }
    else if (events.listenersArray[i].element != “) repeat = true;
  }
  if (repeat) setTimeout(‚events.addListeners()‘,1000);
  else events.addListeners.done = true;
};

V podstatě zde procházíme celé pole, přičemž u každého prvku se pokusíme registrovat daný posluchač. Pokud se nám to nepodaří, spustíme funkci za další vteřinu znovu. Pokud se nám to podaří, vymažeme odkaz na element, aby se příště již registrace posluchače nezkoušela (v tomto případě by bylo lepší zkrátit celé pole o tento prvek, bohužel však IE 5 tuto funkci nepodporuje).

Pokud je prohlížeč schopen registrovat události tímto způsobem, uloží se do vlastnosti events.addListeners.able hodnota true, jinak false, aby měl programátor kontrolu a případně mohl podniknout jiné kroky. Po dokončení všech registrací se do vlastnosti events.addListeners.done přiřadí true.

Příklady použití

Nyní k několika příkladům, jak lze námi vytvořené metody využít:

events.addListener(document.getElementById(‚nasPrvek‘),’click‘,function(e) { alert(‚Došlo k události ‚ + e.type); });
events.removeListener(document.getElementById(‚nasPrvek‘),’click‘,function(e) { alert(‚Došlo k události ‚ + e.type); });
events.listenersArray = Array({ element: ‚document.getElementsByTagName(„input“)[0]‘, type: ‚keypress‘, fcn: f }, { element: ‚document.body‘, type: ‚mousemove‘, fcn: function(e) { mojeFunkce(prom1,prom2,e); } });

Celé naše API pro pokročilou práci s událostmi si můžete stáhnout, abyste je nemuseli přepisovat.

Související články

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *