úterý 7. září 2004

Pořadí událostí v prohlížečích

Rád bych se s Vámi podělil o velmi zajímavou věc. Programuji pro třívrstvou aplikaci prezentační vrstvu webu a narazil jsem na problém s eventami v prohlížečích. Potřeboval jsem vytvořit za pomoci HTML a JS popup kalendář. Chci aby se choval následovně: Na stránce je malý button. Když na něj user klikne zobrazí se absolutně pozicovaný DIV s kalendářem, který je zobrazen dokud je user ukazatelem myši nad kalendářem nebo nad tlačítkem vyvolávajícím kalendář.

To znamená, že na událost onmouseout těchto dvou prvku zavolám JS funkci a ta skryje DIV s kalendářem. Všechno by krásně fungovalo, kdyby ten inkriminovaný DIV neměl v sobě vnořené další elementy (tlačítko pro výběr měsíce, jednotlivé dny....)

Nejlépe to předvedu na příkladu:

        
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head>

  <script language="JavaScript">
    var logID = 0;
    function addLog(message) {
      var log = document.getElementById("console");
      logID++;
      log.value = log.value + logID + " : " + message + "\n"
    }
    function clearLog(){
      var log = document.getElementById("console");
      log.value = "";
      logID = 0;
    }
  </script>

 </head>
 <body style="margin:20px 20px 20px 20px;">
  <div id="outter" align="center" style="border:1px solid black;
     background-color:#AAAAff;width:100px;height:100px"
     onmouseover="addLog('outer DIV - mouse over');"
     onmouseout="addLog('outer DIV - mouse out');">
     
      Outer DIV
      <br /><br />
      <div style="border:1px solid black;
          background-color:#AAFFAA;width:50px;height:50px;margin:auto;"
          onmouseover="addLog('inner DIV - mouse over');"
          onmouseout="addLog('inner DIV - mouse out');">
          
        Inner DIV
      </div>
    </div>
   <br />
   <input onclick="clearLog();" type="button" value="clear LOG" />
   <br />
   <textarea id="console"
       style="width:500px;height:300px;"></textarea>
 </body>
</html>

Ilustrační obrázek

Nyní si projdeme události jak jsou vyvolávány když hýbeme myší podle červené šipky v obrázku:

 
1 : outer DIV - mouse over
2 : outer DIV - mouse out
3 : inner DIV - mouse over
4 : outer DIV - mouse over
 

Překvapivé jsou eventy 2 a 4. Zvláště eventa 2 nám znemožňuje na událost onmouseout outterDivu skrýt outterDiv. Můj původní předpoklad byl, že se eventy chovají tak, že outter mouse over a outter mouse out se vyvolá pouze v případě, že myší najedu a nebo sjedu z tohoto divu avšak jeho "děti/vnořené elementy" nikdy nevyvolají tyto eventy u svého "rodiče/nadřazeného elementu". Nalezl jsem zatím dvě řešení problému.

První řešení

 
//JavaScript  
var mysNadDiv = false;
var delayCallID = 0;
function delayCall(funcCall, delay){
  clearTimeout(delayCallID);
  delayCallID = setTimeout(funcCall,delay);
}
function hideDiv(){
 if (!mysNadDiv) {
  document.getElementById('outter').style.display="none";
 }
}

//do html k outter divu
onmouseover="mysNadDiv=true;"
onmouseout="mysNadDiv=false;delayCall('hideDiv();',200);"

Jde v podstatě o zpožděné zavolání funkce v JS při eventě 2 a než se skutečně zavolá hideDiv tak mezitím eventa 4 znemožní skrytí přes proměnou 'mysNadDiv'. Ve výsledku se outter Div skryje jen když opravdu myš opustí outter Div. clearTimeout je v JS funkci proto, aby se nekumulovalo zbytečně více timerů při rychlém přejíždění myši.

Druhé řešení

Hlídat si pozici myši a vykašlat se na eventy. Outter Div skrýt jen tehdy, když se souřadnice myši dostanou mimo definovanou oblast outer Divem.

Jestli někdo zná lepší řešení, sem s ním. Jen bych rád podotknul, že inner Divů může být klidně 100 a v každém inner Divu můžou být další vnořené elementy a musí Vámi navrhnuté řešení stále fungovat s pokud možno nejméně přidaného kódu.