DOM – objektový model dokumentu IV.

27. února 2002
Matrox G800? SDÍLET NA FACEBOOKU TWEETNOUT
Dnešní díl bude zaměřen prakticky. Ukážeme si užitečný univerzální skript, který nám umožní procházet libovolným dokumentem a zjišťovat vlastnosti všech jeho uzlů.
Minulý díl měl spíše podobu referenční příručky, než tutoriálu - dnes tuto jednostrannost vyvážíme a budeme se věnovat pouze ukázce konkrétního skriptu.

Univerzální skript pro procházení dokumentem a zjišťování vlastností uzlů

Dnešní příklad je poměrně rozsáhlý, proto bude na začátku dobré vysvětlit, z jakých částí se skládá a jak funguje. Ještě před tím doporučuji podívat se na to, jak vypadá výsledek celého snažení. Příklad by měl fungovat v Internet Exploreru verze 5 a výše, v Mozille "nuladevítkových" a vyšších verzí (resp. v Netscape Navigatoru 6 a výše). Teoreticky by měl být funkční i v Konqueroru. Testován byl v IE 6 a Mozille 0.9.8.

V příkladu je využito rámů a skládá se celkem ze 4 html stránek. První stránka pouze definuje rozmístění 3 rámů a stránek, které se budou do rámů načítat.

Zdroj stránky s názvem DOM_walk_example.html:

<html>
<head>
<title>DOM walk example</title>
</head>
<frameset cols="*, 300" onload="frames[`control`].setStart()">
<frame src="DOM_source.html" name="source" scrolling="Auto" />
  <frameset rows="50%,50%">
    <frame src="DOM_control.html" name="control" scrolling="Auto" />
      <frame src="DOM_output.html" name="output" scrolling="Auto" />
  </frameset>
</frameset>
</body>
</html>

V levé části obrazovky je rám s názvem source, do kterého se načítá stránka DOM_source.html. DOM_source.html je HTML dokument, který máme v úmyslu procházet a zjišťovat vlastnosti jeho uzlů. Můžete ho tedy nahradit jakýmkoliv jiným platným HTML dokumentem, není nutné se omezovat na jednoduchý připravený příklad. Pravá část obrazovky je rozdělena na 2 rámy. V horním rámu s názvem control je stránka DOM_control.html, která obsahuje odkazy, které po kliknutí umožňují volně přejít na rodiče, první dítě, následujícího sourozence, atd. kteréhokoliv objektu v dokumentu načteném v rámu s názvem source (v našem příkladu je to právě DOM_source.html). Také obsahuje odkaz nastavit výchozí element, kterým se můžeme vrátit na nastavený výchozí uzel (nastaven je element BODY). K této stránce je připojen kód javascriptu, který zajišťuje všechny výše uvedené akce. Skript je uložen v samostatném souboru DOM_walking_script.js.

Zdroj stránky s názvem DOM_control.html:

<html>
<head>
  <title>Untitled</title>
  <style type="text/css">
  a {
  display: block;
  font-family: Verdana;
  }
  </style>
  <script type="text/javascript" language="JavaScript" src="DOM_walking_script.js"></script>
</head>

<body>
<a href="javascript:setStart()">nastavit výchozí element</a>
<br>
<a href="javascript:goParent()">jit na rodice</a>
<a href="javascript:goPrev()">jit na předchozího sourozence</a>
<a href="javascript:goNext()">jit na následujícího sourozence</a>
<a href="javascript:goFirst()">jit na první dítě</a>
<a href="javascript:goLast()">jit na poslední dítě</a>
<br>
<a href="javascript:goChildren()">najít děti</a>
<a href="javascript:goAttrs()">najít atributy</a>
</body>
</html>

Konečně třetí rám v pravém spodním okraji obrazovky, nazvaný output, obsahuje stránku DOM_output.html. Tato stránka je určena pro zobrazování všech vlastností aktuálního uzlu. Její obsah se mění vždy po přemístění na jiný uzel. Ke změně textového obsahu této stránky budeme používat také vlastností DOM, konkrétně vlastnost data.

Zdroj stránky DOM_output.html:

<html>
<head>
  <title>Untitled</title>
  <style type="text/css">
  div {
  font-family: Verdana;
  }
  span {
  font-weight: bold;
  }
  #arrayCounter {
  visibility: hidden;
  }
  </style>
</head>

<body>
<div id="arrayCounter">
aktuální objekt je <span id="pos">---</span> z <span id="total">---</span>
  <div>
    <a href="javascript:parent.frames[`control`].changePos(false);">< předchozí</a>
    <a href="javascript:parent.frames[`control`].changePos(true);">další ></a>
  </div>
</div>

<br>
<div>nodeName: <span id="nodeName">---</span></div>
<div>nodeValue: <span id="nodeValue">---</span></div>
<div>data: <span id="data">---</span></div>
<div>tagName: <span id="tagName">---</span></div>
<div>className: <span id="className">---</span></div>
<div>id: <span id="id">---</span></div>
<div>title: <span id="title">---</span></div>
<div>nodeType: <span id="nodeType">---</span></div>
<div>name: <span id="name">---</span></div>
<div>value: <span id="value">---</span></div>
</body>
</html>

Všechny elementy na stránce DOM_output.html, se kterými se bude manipulovat jsou označeny pomocí atributu id. Toto jedinečné označení každého elementu umožňuje přesně identifikovat každý element, aby bylo možné k nim jednoduše přistupovat. Názvy id odpovídají názvům daných vlastností DOM, jejichž hodnoty budeme vypisovat. Jak uvidíte později, značně nám to usnadní práci.

Všimněte se elementu <div id="arrayCounter">, jehož obsah je schován pomocí kaskádového stylu - vlastnost visibility je nastavena na hodnotu hidden. Důvod tohoto ukrytí je ten, že obsah tohoto elementu potřebujeme vidět jen při procházení dětí nebo atributů elementu. Obsahuje 2 odkazy, díky kterým můžeme procházet objekty, které jsou vráceny v poli, např. díky vlastnosti childNodes, a také ukazuje pozici aktuálního objektu v poli. Pro tyto příležitosti je obsah tohoto atributu učiněn viditelným, opět pomocí DOM (podrobně se budeme prostředky DOM pro práci se styly a stylesheety zabývat v některém z dalších dílů seriálu).

Rozbor funkcí ve skriptu DOM_walking_script.js

Na začátku skriptu definujeme 4 globální proměnné:

var objekt;
var objekts;
var total;
var pos;

V proměnné objekt budeme uchovávat aktuální objekt, jehož vlastnosti chceme zjišťovat. Proměnná objekts slouží pro uchovávání pole objektů. Využijeme ji v případě, že chceme procházet děti (childNodes) daného uzlu, anebo atributy (attributes) daného elementu.

Stejně tak zbývající 2 proměnné jsou využívány při procházení pole objektů - proměnná total nese celkový počet prvků pole, tedy počet dětí nebo atributů. Proměnná pos uchovává pozici aktuálně zobrazeného objektu v poli.

Celý proces startuje funkce setStart(). Tato funkce nastavuje výchozí objekt, od kterého se odvíjí celé další procházení dokumentem. Funkce je volána hned při nahrání celé aplikace do prohlížeče - všimněte si atributu onload v tagu <frameset> na stránce DOM_walk_example.html. Zde dochází k volání funkce setStart(). Samozřejmě je nutné ji adresovat do správného rám. Důležité to je z důvodu, aby hned na začátku byl nastaven objekt, od kterého můžeme začít procházení dokumentem (v opačném případě by odkazy ve stránce DOM_control.html nebyly funkční).

Funkce setStart()

function setStart() {
  var elem = `body`;
  alert(`počáteční nastavení : element `+elem);
  objekt = top.frames[`source`].document.getElementsByTagName(elem)[0];
  viewCurrent();
}

Na začátku je do proměnné elem uložen text "body". Následně je aktivováno okno s upozorněním, že výchozím objektem je element BODY. Pak je do proměnné objekt uložen právě element BODY. Syntaxe je poněkud zdlouhavá, neboť napřed je nutné se propracovat k nejvyššímu správci oken (top), nato vybrat příslušný rám, kde je načten dokument, který chceme procházet (frames[`source`]) a nakonec získat element dle jména tagu (nezapomínejme, že metoda getElementsByTagName vrací pole objektů, takže přestože si u HTML dokumentu můžeme být jisti, že element BODY se tam nachází pouze jednou, musíme specifikovat index, samozřejmě je to [0]). Závěrem je volána funkce viewCurrent(), která se stará o zobrazení hodnot všech vlastností aktuálního objektu.

Jistě vidíte, že jako výchozí element může být nastaven kterýkoliv jiný element v dokumentu. Toto konkrétní řešení je zvoleno z toho důvodu, že element BODY bude přítomen v každém HTML dokumentu. Funkci je možno modifikovat i tak, že výchozí element bude určen například pomocí metody getElementById, princip to nijak nemění.

Funkce viewCurrent()

Nyní pojďme k výše zmíněné funkci viewCurrent(), která se stará o zobrazení všech vlastností aktuálního objektu. Tato funkce má centrální postavení, je používána i všemi ostatními funkcemi volanými při přechodu na jiný objekt.

function viewCurrent() {
  var nazev = new Array(8);
  nazev[0] = `nodeName`;
  nazev[1] = `nodeValue`;
  nazev[2] = `data`;
  nazev[3] = `tagName`;
  nazev[4] = `className`;
  nazev[5] = `id`;
  nazev[6] = `title`;
  nazev[7] = `nodeType`;
  nazev[8] = `name`;
  nazev[9] = `value`;
 
  var vlastnost = new Array();
  vlastnost[nazev[0]] = objekt.nodeName;
  vlastnost[nazev[1]] = objekt.nodeValue;
  vlastnost[nazev[2]] = objekt.data;
  vlastnost[nazev[3]] = objekt.tagName;
  vlastnost[nazev[4]] = objekt.className;
  vlastnost[nazev[5]] = objekt.id;
  vlastnost[nazev[6]] = objekt.title;
  vlastnost[nazev[7]] = objekt.nodeType;
  vlastnost[nazev[8]] = objekt.name;
  vlastnost[nazev[9]] = objekt.value;
 
    for (i=0; i<nazev.length; i++) {
    parent.frames[`output`].document.getElementById(nazev[i]).firstChild.data = vlastnost[nazev[i]];
    }
   
  parent.frames[`output`].document.getElementById(`arrayCounter`).style.visibility = "hidden";
}

Největší část celého kódu této funkce zabírá nastavení hodnot prvků dvou polí. První pole je uloženo v proměnné nazev. Určuje názvy jednotlivých DOM vlastností, které budeme zjišťovat. Prvky druhého pole uloženého v proměnné vlastnost už nesou konkrétní vlastnosti aktuálního objektu. Jedná se o asociativní pole, kde index každého prvku tvoří název dané vlastnosti. Místo vypisování názvu použijeme prvky předchozího pole nazev. Např.:

vlastnost[nazev[0]] = objekt.nodeName;

uloží do proměnné vlastnost[`nodeName`] hodnotu DOM vlastnosti nodeName objektu uloženého v proměnné objekt.

Dále následuje cyklus for, který zajišťuje vypsání všech vlastností objektu do stránky DOM_output.html. Cyklus se opakuje tolikrát, dokud nejsou vyčerpány všechny vlastnosti. Jejich počet je stejný jako počet jejich názvů, proto můžeme použít v podmínce cyklu hodnotu výrazu nazev.length, tedy délku (počet prvků) pole nazev. Pak je potřeba dostat se ke stránce DOM_output.html, která je načtena v rámu s názvem output. To zajišťuje výraz parent.frames[`output`], který nám dovolí přenést se ke stránce, ve které budou vypisovány vlastnosti objektu. Pomocí document.getElementById(nazev[i]) dorazíme až k příslušnému elementu, jehož textový obsah budeme měnit. Zde je vidět smysl toho, že atributy id těchto elementů máme pojmenované dle příslušných vlastností, jejichž hodnoty budeme vypisovat. Můžeme tak v parametru metody getElementById použít prvek pole nazev, jehož hodnota odpovídá hledanému id. Nakonec pomocí vlastnosti firstChild.data dojdeme k textovému obsahu elementu (přesněji řečeno textové hodnotě vnořeného objektu typu text). Jak jsme si uvedli minule, vlastnost data můžeme použít i k nastavení textu - využijeme toho a dosadíme hodnotu proměnné vlastnost[nazev[i]], ve které je uložena hodnota příslušné DOM vlastnosti.

Možná jste si všimli, že elementy v dokumentu DOM_output.html, které máme "připraveny" pro vypisování DOM vlastností, obsahují text ---, např.:

<span id="nodeName">---</span>

Proč nejsou tyto elementy prázdné? Důvod je ten, že pomocí vlastnosti data chceme měnit textový obsah objektu typu text. Pokud by tedy neobsahovaly žádný text, pro DOM by ani neexistoval žádný objekt typu text a neměli bychom tedy co měnit… Proto každý element obsahuje tři pomlčky (text samozřejmě může být libovolný). Žádné tři pomlčky bychom ovšem vkládat nemuseli, kdybychom uměli vytvářet vlastní nové uzly a přidávat je do dokumentu. To bude obsahem dalšího dílu seriálu a proto vám v tomto skriptu nechci zamotávat hlavu novými technikami.

Poslední řádka funkce viewCurrent() nastavuje css vlastnost visibility na hodnotu hidden u elementu <div id="arrayCounter">. Ovládat kaskádový styl pomocí DOM jsme ještě nezkoušeli - podrobně se na to podíváme v některém z dalších dílů. Nyní uveďme pouze význam této akce právě v této funkci - Jak již bylo uvedeno, obsah elementu <div id="arrayCounter"> potřebujeme zobrazit pouze při procházení polem objektů - funkce viewOneFrom(), která se na procházení polem objektů podílí (a která bude popsána později), naopak nastavuje vlastnost visibility u tohoto elementu na hodnotu visible. Ovšem činí tak až poté, co zavolá funkci viewCurrent() (která je volána při každém přechodu na jiný objekt), která obsah elementu <div id="arrayCounter"> opět schová. Výsledek je ten, že "schování" je provedeno vždy, ovšem v případě potřeby (tj. při procházení polem objektů) je nakonec provedeno i odkrytí.

Funkce goParent(), goPrev(), goNext(), goFirst() a goLast()

Dále následuje sada několika podobných funkcí založených na stejném principu. Jedná se o funkce goParent() volaná při přechodu na rodičovský objekt, goPrev() volaná při přechodu na předchozího sourozence, goNext() volaná při přechodu na následujícího sourozence, goFirst() volaná při přechodu na první dítě objektu, a nakonec goLast(), která zajišťuje přechod na poslední dítě objektu. Jejich funkci si vysvětlíme na příkladu první z nich, tedy goParent().

function goParent() {
  if (objekt.parentNode!=null) {
  objekt = objekt.parentNode;
  viewCurrent();
  } else {
  alert (`aktuální objekt nemá rodiče`);
  }
}

V podmínce příkazu if je testováno, jestli aktuální objekt má rodiče (objekt.parentNode), tj. nenabývá hodnoty null. Pokud je výraz vyhodnocen jako true, čili objekt má rodiče, je do proměnné objekt uložen tento rodičovský objekt. Poté je zavolána funkce viewCurrent(), která se postará o aktualizaci údajů o vlastnostech objektu. Pokud je výraz vyhodnocen jako false (objekt rodiče nemá), je tato skutečnost nahlášena uživateli pomocí funkce alert. Ostatní čtyři funkce jsou zcela analogické.

Pro procházení pole dětí objektu (childNodes) a pole atributů elementu (attributes) jsou volány funkce goChildren() a goAttrs().

Funkce goChildren(), viewArray(), viewOneFrom()

function goChildren() {
  if (objekt.hasChildNodes()) {
  objekts = objekt.childNodes;
  viewArray();
  } else {
  alert (`Aktuální objekt nemá děti`);
  }
}

V podmínce příkazu if je použita metoda hasChildNodes(), se kterou jsme ještě neměli možnost se seznámit. Tato metoda vrací true, pokud objekt má děti a false, pokud nikoliv. Když je tímto způsobem indikováno, že aktuální objekt děti má, pak je do proměnné objects uloženo pole těchto dětských objektů (objekt.childNodes). Poté je zavolána funkce viewArray(). Tato funkce se postará o další dílčí úkoly.

function viewArray() {
  pos = 0;
  total = objekts.length;
  viewOneFrom();
}

Jak vidíte, zas tolik toho na práci nemá. Pouze nastaví hodnotu proměnné pos na nulu (jelikož bude zobrazen první objekt z pole, tedy ten s indexem 0), spočítá počet prvků pole (objekts.length) a uloží jej do proměnné total. Pak její práce končí a žezlo je předáno další funkci viewOneFrom().

function viewOneFrom() {
  var position = pos+1;
  objekt = objekts[pos];
  viewCurrent();
  var output_doc = parent.frames[`output`].document;
  output_doc.getElementById(`pos`).firstChild.data = position;
  output_doc.getElementById(`total`).firstChild.data = total;
  output_doc.getElementById(`arrayCounter`).style.visibility = "visible";
}

Tato funkce obstarává zbytek práce. Do lokální proměnné position uloží hodnotu proměnné pos zvýšenou o jedničku (to je z toho důvodu, že budeme chtít zobrazit pozici aktuálního objektu v poli číslovaného od jedničky, nikoliv od nuly). Poté do proměnné objekt uloží prvek pole objekts. Jako index je použita aktuální posice. Následně je volána funkce viewCurrent(), která zajistí zobrazení všech DOM vlastností aktuálního objektu. Dále je potřeba doplnit text o pozici aktuálního objektu v poli a celkový počet prvků pole. Do pomocné proměnné output_doc si uložíme "cestu" ke stránce DOM_output.html, abychom ji nemuseli vypisovat třikrát za sebou. Pak je vyhledán element, do jehož textového obsahu doplníme výše zmíněnou posici. Totéž provedeme s celkovým počtem prvků v poli. Nakonec u elementu <div id="arrayCounter"> nastavíme css vlastnost visibility na hodnotu visible, aby jeho obsah byl viditelný.

Funkce goAttrs()

Nyní se dostáváme k funkci goAttrs():

function goAttrs() {
  if (objekt.nodeType==1){
    if (objekt.attributes.length>0) {
    objekts = objekt.attributes;
    viewArray();
    } else {
    alert (`aktuální objekt nemá žádné atributy definovány`);
    }
  } else {
  alert (`aktuální objekt není typu element`);
  }
}

V první podmínce je testováno, zda-li je aktuální objekt typu element (objekt.nodeType==1). Pokud ano, zjišťujeme, kolik atributů daný element má (objekt.attributes.length). Pokud alespoň jeden, uložíme pole atributů do proměnné objekts a zavoláme funkci viewArray(). Poté už probíhá zpracování stejným způsobem, jako u pole dětí (ve funkci goChildren()).

Důležitá poznámka k funkčnosti celého příkladu - pokud se přesuneme pomocí funkce goAttrs() (tedy po kliknutí na odkaz "najít atributy") do pole atributů nějakého elementu, již "není cesty zpět". Nebude fungovat přechod na rodiče, předchozího či následujícího potomka, atd. Je to zcela v souladu se skutečností, že objekty typu attr nejsou součástí hlavního stromu DOM objektů. Pokud se budeme chtít vrátit k procházení dokumentu, jediná možnost je přes odkaz "nastavit výchozí element", kterým se dostaneme k elementu BODY.

Další zvláštností, na kterou je potřeba upozornit, je rozdílná interpretace vlastnosti attributes v IE a v Mozille, zmíněná v minulém dílu našeho seriálu. Při použití IE se to názorně projeví tím, že poté, co vstoupíte do pole atributů nějakého elementu, bude jejich počet blízký stovce. Naproti tomu v Mozille budete mít k dispozici jen ty atributy, které daný element skutečně má.

Funkce changePos(dopredu)

Nakonec nám zbyla funkce changePos(dopredu), která je použita při procházení pole dětí nebo atributů pro přechod na následující nebo předchozí objekt.

function changePos(dopredu) {
  if ((dopredu)&(pos<(objekts.length-1))) {
  pos++;
  viewOneFrom();
  }
  else if ((!dopredu)&(pos>0)) {
  pos--;
  viewOneFrom();
  } else {
  alert (`Bylo dosaženo prvního anebo posledního prvku pole.`);
  }
}

Na rozdíl od všech ostatních funkcí je v parametru předávána hodnota proměnné. Tato proměnná s názvem dopredu nabývá hodnot true|false. Pokud je true, probíhá přesun na následující objekt v poli, pokud false, proběhne přesun na předchozí objekt. Pro obě možnosti je ještě testováno, zda-li nebylo dosaženo prvního nebo posledního objektu v poli. Pokud nebylo, proběhne zvýšení (resp. snížení) hodnoty proměnné pos o jedničku a je zavolána funkce viewOneFrom().

To je tedy celý skript. Doufám, že pro vás byl tento příklad zajímavý. I pro ty, kteří se nechtějí zabývat tím, jak přesně celý skript pracuje, to může být užitečná pomůcka pro experimenty s DOM a pro testování, jakým způsobem lze procházet libovolným konkrétním HTML dokumentem (připomínám, že místo stránky DOM_source.html můžete použít jakoukoliv jinou platnou HTML stránku). Při experimentování mnozí z vás jistě objeví, že existují určité odlišnosti v tom, jaký strom DOM objektů vytváří Internet Explorer a jaký Mozilla. Jako procvičení si můžete zkusit nalézt tyto rozdíly a zamyslet se nad tím, na co je potřeba si dávat pozor, pokud chceme zajistit "cross-browser" funkčnost našich DOM skriptů.

Příští díl bude věnován pro všechny z vás jistě zajímavému tématu - vytváření nových objektů a jejich přidávání do dokumentu.

Váš názor Další článek: Matrox G800?

Témata článku: Software, Prohlížeče, Internet, Programování, Internet Explorer, Funkce, Objektový, Dítě, Doma, Total, Element, Code, První stránka, Stejný prvek, Alert, První funkce, Dílčí úkol, Přechod, Mode, Elsa, Zobrazený prvek, Head, Libovolný bod, Objekt, Dok