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

6. března 2003

Když jsem před několika dny četl předchozí článek o zpracování událostí, ihned se mi v textu nastíněná idea zalíbila. Přes zdařilý nápad jsem se kriticky vyjádřil, že řešení problému jde opačným směrem, než bych si osobně představoval. Od této diskuse nebylo daleko k myšlence sjednocení modelu událostí dnešních prohlížečů podle W3C standardu.

Ve zmíněném článku byl totiž upřednostňován model IE na úkor ostatních prohlížečů, respektive standardu. Princip níže popisované knihovny je v základu stejný (nahrazení, respektive vytvoření vykonávajících „mezifunkcí“), s tím rozdílem, že tento postup je rozvíjen dále. Jak tento experiment dopadl, se můžete nyní přesvědčit.

Nejprve však malé ohlednutí proč vlastně tento fix vznikl. Jak jste se již mnozí v jiných článcích dočetli, model událostí patří k té oblasti, která se u prohlížečů liší, často naprosto diametrálně. Primátem je v tomto Internet Explorer, mezi jehož největší nedostatky patří:

  • chybějící implementace zachytávání událostí
  • odlišný způsob dynamické registrace (odstranění) posluchačů
  • odlišnost a absence atributů objektu Event
  • používání přípon „on“

Prohlížeč Mozilla (jádro), je na tom o poznání lépe, nicméně i to obsahuje jednu nepřesnost. Pakliže chceme dostát našemu požadavku identického chování, nesmíme ji opomenout. Podle W3C se ovladač zachytávané události nemá vyvolávat, jestliže je událost vyvolána přímo elementem, jemuž byl posluchač registrován. Jinými slovy, registrujete-li ovladač způsobem elm.addEventListener('mousedown', klik, true), přímé kliknutí na element elm nesmí vyvolat funkci klik(). Tento aspekt jádro Mozilly porušuje. Co se týče nové Opery 7, ta podle všeho doporučení respektuje.

Registrace (odstranění) posluchače

Narozdíl od předchozího textu nebudu vytvářet zvláštní objekt obsahující nové metody addEventListener/removeEventListener. Abychom ve výsledku nepoznali rozdíl při práci na Mozille a IE, nové metody spojíme přímo s elementy. Začneme tedy funkcí fixElement(), která elementu přidá „softwarově“ vytvořené metody addEventListener a removeEventLister.

function fixElement(elm)
{
 if(document.all && !document.addEventListener)
 {
   elm.addEventListener = function(handle, fce, typ)
   { ……}
   elm.removeEventListener = function(handle, fce, typ)
   { ……}
  }

Podle první podmínky vidíte, že se budu nejprve zabývat metodami pro IE. Metoda addEventListener je náhrada existujícího attachEvent s tím, že vykoná ještě kroky, které budeme později nutně potřebovat. Především je to vytvoření dvou matic typů událostí a jim odpovídajících ovladačů (funkcí), které jsou k danému elementu registrovány. Matice proto, neboť typů událostí je více (řádky), stejně tak k jednomu typu události může být pověšeno více funkcí (sloupce). Jedna matice odpovídá událostem zachytávaným, druhá bublajícím. Při vyvolání této metody se tedy vloží reference na funkci na odpovídající místo v matici a poté pomocí attachEvent „fyzicky“ registruje posluchač s ovladačem makeIE. Ten funguje jako zmiňovaná „mezifunkce“ obstarávající zbylou režii.

elm.addEventListener = function(handle, fce, typ)
{
// Existuje již řádek toho typu události ?
if(!this.captures[handle]) this.captures[handle]=new Array();
if(!this.bubbles[handle]) this.bubbles[handle]=new Array();
if(this.captures[handle].length==0 && this.bubbles[handle].length==0)
eval(„this.attachEvent(\“on\“+handle, makeIE);“);
if(typ)
{
  this.captures[handle][this.captures[handle].length] = fce;
  this.captures[handle][this.captures[handle].length] = this;
}
else
{
  this.bubbles[handle][this.bubbles[handle].length] = fce;
  this.bubbles[handle][this.bubbles[handle].length] = this;
}
}

Metoda removeEventListener je podobného ražení, funguje však opačným směrem. Podle zadaných parametrů nalezne v matici odpovídající typ události (řádek) a název funkce (sloupec). Odstranění z matice probíhá trošku kostrbatěji, ale bohužel IE5 nezná metodu splice. Na závěr musíme pomocí detachEvent zrušit opravdový posluchač události, pokud je řádek v maticích daného typu události prázdný.

….
if(this.captures[handle].length==0 && this.bubbles[handle].length==0)
{
  eval(„this.detachEvent(\“on\“+handle, makeIE);“);
}

U Mozilly je situace odlišná. Především tak složité řešení nepotřebujeme, navíc metody již existují. Našim cílem je před vyvoláním funkce vložit podmínku, opravující vyřčené tvrzení. Řešení spočívá v tom, že nově vytvořená metoda addEventListener2 dynamicky vytvoří novou funkci (mezifunkce). Ta obsahuje ve svém těle požadovanou podmínku, která, je-li splněna, vyvolá původní funkci. Tato mezifunkce se pak registruje jako ovladač dané události.

Zpracování události u IE

Jak jste doufám z předchozích řádků pochopili, každé IE události je přirazena funkce makeIE, jejíž účel je zřejmý. Uměle provést všechny fáze ohlášení události a doplnit objekt Event o potřebné parametry tak, aby co nejvíce splňoval doporučení W3C. K těmto krokům ale potřebuje získat některé informace. Element, který byl cílem události (event.target) známe, byť pod jiným názvem (event.srcElement). Aby funkce byla schopna provést odhlášení od document k elementu (zachytávání) a od elementu k document (bublání), musí znát cestu, respektive elementy, které sdílí větev v stromové hierarchii dokumentu. Jednoduchým cyklem cestu najdeme a u každého elementu, který po cestě „potkáme“, zaznamenáme registrované posluchače a podle typu je vložíme do seznamu toCapture nebo toBubble.


while(temp)
{
  if(temp.bubbles[event.type])toBubble = toBubble.concat(temp.bubbles[event.type]);
  if(temp.captures[event.type])toCapture = toCapture.concat(temp.captures[event.type]);
  if(temp.nodeName == „HTML“) {temp = document;}
  else {temp = temp.parentNode;}
}

Nyní již máme všechna potřebná data a nic nebrání doplnění objektu Event o standardní prvky, emulaci zachytávání zpětným průchodem seznamu toCapture a emulaci bublání průchodem seznamu toBubble. V závěru pak musíme zabránit opravdovému bublání, neboť by se tento postup mohl nežádaně opakovat.


  /// Faze zachytavani
  event.eventPhase=1;
  for(x=toCapture.length-1; (x>=0 && event.stop==false); x=x-2)
  {
    event.currentTarget = toCapture[x];
    toCapture[x-1](event);
  }
….

Použití

Cílem této knihovny je zajistit stejné chování v prohlížečích jádra Internet Explorer 5+ a jádra Mozilla 0.9.1+. Před využíváním je třeba (nejlépe po načtení stránky) upravit všechny elementy dokumentu. Pro tento případ slouží funkce fixAllElements(), procházející celý dokument a aplikující fixElement() na každý jeho prvek. Jako výsledek vám bude k dispozici v rámci možností jednotné rozhraní a chování modelu událostí. Jako drobnou ilustraci doporučuji vytvořený testovací příklad events.htm, případně jednoduchou aplikaci drag.htm.

Vše má i své stinné stránky. Pokud se podíváte do zdroje, zjistíte, že zpracování události si žádá větší režii, víceméně danou požadavky na standardní chování. To se na starých konfiguracích projevuje delší odezvou u náročných řetězení, například typu drag&drop. Ačkoli se prohlížeče chovají „standardně“, nemohu zaručit, že u všech doposud napsaných skriptů postačí jednoduché odstranění dělících podmínek. Spíše bych skript doporučil jako výchozí bod pro psaní dalších aplikací.

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.

Další článek bolatice.cz
Š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 *