Podstawy#
Elementy i trybuty#
Z perspektywy znaczników elementy i ich atrybuty to chyba jedne z najpopularniejszych klocków budulcowych, jakie występują w całym dokumencie X(HT)ML. Reguły związane z ich prawidłowym tworzeniem i użytkowaniem opisałem w kursie HTML 4.01 (działy "Szkielet dokumentu - Wstęp", "Szkielet dokumentu - Dobre nawyki", "Formatowanie treści - Podział elementów") oraz kursie HTML5.
Z uwagi na to, że stanowią one nierozerwalny byt, gdzie spoiwem między nimi jest mapa nazwanych atrybutów, to wszystkim tym trzem zagadnieniom poświęcam jeden wspólny opis.
Elementy#
Specyfikacja DOM4 definiuje jeden bazowy interfejs Element z najbardziej ogólnymi właściwościami i metodami dostępnymi dla wszystkich elementów, jakie mogą pojawić się dokumentach X(HT)ML. Nic jednak nie stoi na przeszkodzie, by kolejne specyfikacje definiowały dla elementów (lub całych ich grup) bardziej wyspecjalizowane interfejsy dziedziczące po Element, z ewentualnym tworzeniem głębszych łańcuchów dziedziczenia.
Idealnym przykładem będzie specyfikacja HTML5, która definiuje dodatkowy interfejs HTMLElement (dziedziczący po Element) dla wszystkich elementów z przestrzenią nazw HTML. Ponadto każdy specyficzny element HTML (np. p
, div
, pre
, itd.) może posiadać swój własny interfejs, który będzie dziedziczył z HTMLElement lub jego pochodnej. Specyfikacja HTML5 wprowadza także dodatkowy interfejs HTMLUnknownElement dla elementów z przestrzenią nazw HTML, które nie zostały zdefiniowane w specyfikacji. Nie ma on żadnych dodatkowych poleceń, dziedziczy jedynie z HTMLElement, no ale dzięki niemu możemy szybciej wykrywać wszystkie niezdefiniowane elementy. W przypadku tej specyfikacji dobór właściwego interfejsu elementu dla elementów HTML odbywa się zgodnie ze ściśle ustalonymi krokami.
Istnieją spore nieścisłości między przeglądarkami internetowymi przy implementowaniu właściwych interfejsów dla elementów HTML uznawanych za przestarzałe i niezgodne, jak chociażby w przypadku applet
czy marquee
(HTML - Bug 27877, HTML - Bug 1015).
Prosty przykład:
<script>
var newOL = document.createElement("ol"); // nowy element w przestrzeni nazw HTML (zdefiniowany w specyfikacji)
var newB = document.createElement("b"); // nowy element w przestrzeni nazw HTML (zdefiniowany w specyfikacji)
var newX = document.createElement("X"); // nowy element w przestrzeni nazw HTML (niezdefiniowany w specyfikacji)
var newY = document.createElementNS("noname", "Y"); // nowy element w zmyślonej przestrzeni nazw
document.write(newOL); // [object HTMLOListElement]
document.write("<br>");
document.write(newOL.namespaceURI); // http://www.w3.org/1999/xhtml (właściwość dziedziczona z interfejsu Element)
document.write("<br>");
document.write(newOL.click); // function click() { [native code] } (metoda dziedziczona z interfejsu HTMLElement)
document.write("<br>");
document.write(newOL.start); // 1 (właściwość z interfejsu HTMLOListElement)
document.write("<br><br>");
document.write(newB); // [object HTMLElement]
document.write("<br>");
document.write(newB.namespaceURI); // http://www.w3.org/1999/xhtml (właściwość dziedziczona z interfejsu Element)
document.write("<br>");
document.write(newB.click); // function click() { [native code] } (metoda z interfejsu HTMLElement)
document.write("<br>");
document.write(newB.start); // undefined (brak właściwości w łańcuchu dziedziczenia)
document.write("<br><br>");
document.write(newX); // [object HTMLUnknownElement]
document.write("<br>");
document.write(newX.namespaceURI); // http://www.w3.org/1999/xhtml (właściwość dziedziczona z interfejsu Element)
document.write("<br>");
document.write(newX.click); // function click() { [native code] } (metoda dziedziczona z HTMLElement)
document.write("<br>");
document.write(newX.start); // undefined (brak właściwości w łańcuchu dziedziczenia)
document.write("<br><br>");
document.write(newY); // [object Element]
document.write("<br>");
document.write(newY.namespaceURI); // noname (właściwość z interfejsu Element)
document.write("<br>");
document.write(newY.click); // undefined (brak właściwości w łańcuchu dziedziczenia)
document.write("<br>");
document.write(newX.start); // undefined (brak właściwości w łańcuchu dziedziczenia)
</script>
Mapa nazwanych atrybutów#
Elementy są stale powiązane ze swoją listą atrybutów, którą wyraża mapa nazwanych atrybutów, i mapa ta również operuje na tej samej liście atrybutów. Po stronie skryptu nie ma możliwości utworzenia nowej mapy nazwanych atrybutów, ale dla danego elementu można ją pobrać za pomocą właściwości Element.attributes
. Szczególną cechą mapy jest to, że pozwala ona wydobywać atrybuty zarówno poprzez indeksy numeryczne jak również poprzez nazwy kwalifikowane samych atrybutów. Jeśli element nie posiada żadnych atrybutów to jego mapa nazwanych atrybutów jest pusta.
Planowano zmianę mylącej nazwy interfejsu z "NameNodeMap" na bardziej odpowiadającą "NamedAttrMap", ale ze względu na kompatybilność wsteczną szybko się z tego wycofano. W ramach rekompensaty postanowiłem używać w kursie prawidłowego określenia "mapa nazwanych atrybutów".
Mapa nazwanych atrybutów jest aktualna (live), podobnie zresztą jak kolekcje czy zestawy. Każda zmiana stanu atrybutów w elemencie ma bezpośredni wpływ na obiekt mapy, nawet jeśli obiekt ten utworzony/pobrany został przed wykonaniem zmiany.
Prosty przykład:
<script>
var html = document.documentElement; // referencja do elementu html
var allAttr = html.attributes; // pobieramy listę atrybutów
document.write(allAttr); // [object NamedNodeMap]
document.write("<br>");
document.write(allAttr.length); // 0
document.write("<br><br>");
// Ustawiamy nowy atrybut
html.setAttributeNS("www.test.com", "p:AAA", "Test");
document.write(allAttr.length); // 1
document.write("<br>");
document.write(allAttr[0].value); // Test
document.write("<br>");
document.write(allAttr.getNamedItemNS("www.test.com", "AAA").value); // Test
</script>
Ograniczenie liczby atrybutów w elementach#
Żadna z obecnych lub wcześniejszych specyfikacji DOM, włącznie z aktualną specyfikacją HTML5 (3.2.3.1 Attributes, 12.1.2.3 Attributes), nie wprowadza jakichkolwiek restrykcji dla liczby atrybutów w elementach. Jedynym z negatywnych skutków zbyt dużej liczby atrybutów w elementach może być dłuższy czas ich przetwarzania, ale jest to silnie uzależnione od danego silnika przeglądarki.
Trzeba wyraźnie zaznaczyć, że specyfikacja HTML5 zachęca autorów poszczególnych implementacji do nienakładania żadnych arbitralnych ograniczeń w tym obszarze (12.2.5 Tree construction), choć z drugiej strony informuje o tym, że niektóre aplikacje klienckie nakładają pewne ograniczenia, co ma zapewnić ochronę przed nadmiernym zużyciem pamięci RAM lub umożliwić pracę zgodną z limitami nakładanymi przez daną platformę.
W ujęciu praktycznym wszystko zależy o danej przeglądarki internetowej. Przykładowo Chrome lub IE nie wprowadzają żadnych restrykcji, kiedy Firefox zezwala jedynie na 1023 atrybuty w elementach, i po przekroczeniu tej wartości zrzuca wyjątek (Mozilla - Bug 400800). Jeszcze ciekawiej wygląda sytuacja w przypadku Opery (Presto), która zawsze ustawia maksymalnie 255 atrybutów, i po przekroczeniu tej wartości usuwa wszystkie poprzednie atrybuty (prócz pierwszego z indeksem 0), a następnie wszystkie kolejne atrybuty traktuje jak nowe (z zachowaniem maksymalnego limitu 255 atrybutów).
Prosty przykład:
<script>
try{
var el = document.createElement("div");
var maxAttr = 1023; // UWAGA: Firefox powyżej 1023 zrzuca wyjątek
var maxAttrReal = maxAttr - 1;
for (var i = 0; i < maxAttr; i++){
el.setAttribute("x" + i, "test");
}
document.write("wszystkie atrybuty: ", el.attributes.length);
document.write("<br>");
document.write("el ma attr_x0: ", el.hasAttribute("x0"));
document.write("<br>");
document.write("el ma attr_x1: ", el.hasAttribute("x1"));
document.write("<br>");
document.write("el ma attr_x" + maxAttrReal + ": ", el.hasAttribute("x" + maxAttrReal));
//console.log(el); // UWAGA: zwrócenie elementu z dużą liczbą atrybutów (np. w IE11) może zająć nieco czasu
}
catch(e){
document.write(e);
document.write("<br>");
document.write(e.name); // NS_ERROR_FAILURE
document.write("<br>");
document.write(e.constructor); // function Object() { [native code] }
}
</script>
Unikatowość i uporządkowanie atrybutów w elementach#
Pojęcie unikatowości # (unique) atrybutów w elementach wynika bezpośrednio z definicji języków znacznikowych (HTML lub XML), które mówią, że atrybuty (o tej samej przestrzeni nazw i nazwie lokalnej) nie mogą się powtarzać w danym elemencie. Szerszy komentarz wydaje się zbędny.
Trzeba jednak podkreślić, że atrybuty w liście atrybutów skojarzonej z danym elementem (i w konsekwencji także w jego mapie nazwanych atrybutów) są uporządkowane # (ordered), co oznacza, że atrybuty znajdują się w stałym porządku. Dodawanie nowych atrybutów nie powinno wpływać na już istniejący porządek, przez co kolejność zastosowana przez programistę zostaje zachowana.
Uporządkowania nie należy mylić z sortowaniem, które zmienia porządek atrybutów w liście atrybutów.
Prosty przykład:
<script>
var html = document.documentElement; // referencja do elementu html
html.setAttributeNS("www.test1.com", "ID", "Test1"); // Opera (Presto) niewłaściwie ustawia tutaj wartość undefined
html.setAttributeNS("www.test2.com", "Class", "Test2");
html.setAttribute("id", "Identyfikator");
html.className = "Klasa1 Klasa2 Klasa3";
html.setAttributeNS("www.test3.com", "A:NEW", "Test3");
html.setAttributeNS("www.test4.com", "a:new", "Test4");
var allAttr = html.attributes; // pobieramy listę atrybutów
for (var i = 0; i < allAttr.length; i++){
var attr = allAttr[i];
document.write(attr.namespaceURI + " , " + attr.prefix + " , " + attr.name + " , " + attr.value + "<br>");
}
</script>
Na chwilę obecną wszystkie aktualne przeglądarki przestrzegają uporządkowania atrybutów w elementach, z wyjątkiem IE, która działa po swojemu.
Odzwierciedlenie zawartości atrybutu poprzez dedykowaną właściwość#
Na dzień dzisiejszy istnieje wiele sposobów pozwalających odczytywać lub zmieniać wartość konkretnego atrybutu w danym elemencie. Większość z nich to nic innego jak specyficzne metody wywoływane bezpośrednio na elemencie lub jego mapie nazwanych atrybutów. W pewnych sytuacjach (o ile to możliwe) wygodniejsze okazuje się tzw. odzwierciedlenie zawartości atrybutu poprzez dedykowaną właściwość. Poszczególne specyfikacje mogą definiować nowe właściwości (czyli atrybuty IDL) odzwierciedlające zawartość jakiegoś atrybutu w elemencie, jeśli tylko zajdzie taka potrzeba.
W przypadku specyfikacji DOM4 jawnie zdefiniowano jedynie dwie właściwości Element.id
oraz Element.className
, które odzwierciedlają zawartość odpowiadających im atrybutów z nazwami "id"
i "class"
. Należy zauważyć, że w HTML5 większość atrybutów (jak nie wszystkie) korzysta z tej techniki, dlatego poniższy zapis składniowy dla odczytywania/zapisywania wartości dowolnego atrybutu ze specyfikacji DOM4 i HTML5 będzie uniwersalny:
var attr_value = element.attrName; // getting element.attrName = new_value; // setting
gdzie poszczególne człony oznaczają:
- attr_value - łańcuch znakowy reprezentujący pobraną wartość atrybutu.
- element - węzeł będący obiektem kontekstu.
- attrName - nazwa atrybutu, którą umieszcza się bezpośrednio za kropką (dookreśleniu), bez otaczających cudzysłowów czy apostrofów, identycznie jak dla dowolnej właściwości obiektów.
- new_value - łańcuch znakowy reprezentujący nową wartość atrybutu.
Jeśli nie musimy zmieniać lub odczytywać wartości atrybutów umieszczanych w konkretnych przestrzeniach nazw, to najprościej skorzystać z powyższej formy, zamiast wywoływać dłuższe polecenia Element.getAttribute()
czy Element.setAttribute()
. Oczywiście trzeba być świadomym, że niektóre atrybuty mają inne odpowiedniki właściwościowe w DOM, tak jak przytoczony tutaj atrybut z nazwą "class"
i odpowiadająca mu właściwość Element.className
.
Wprawne oko szybko zauważy drobną różnicę miedzy obydwoma rozwiązaniami. Metody pobierające wartość z niezdefiniowanych atrybutów w elementach zawsze zwracają null
, kiedy technika odzwierciedlenia zawartości zwraca pusty łańcuch znakowy, i przez to nie należy jej używać do wykrywania obecności atrybutów w elementach.
Geneza i przeznaczenie unikatowego identyfikatora w elementach#
Unikatowy identyfikator to specjalna wartość przypisywana do konkretnego elementu. Dzięki niej możliwy jest szybki dostęp do dowolnego elementu spośród wielu elementów w drzewie węzłów. Po stronie implementacyjnej uzyskuje się to poprzez utworzenie dla każdego drzewa węzłów (np. dokumentu lub fragmentu dokumentu) osobnego wykazu ze wszystkimi elementami posiadającymi unikatowy identyfikator, przez co późniejszy do nich dostęp jest niemalże natychmiastowy.
Osobną kwestią pozostaje sposób ustawiania unikatowego identyfikatora w elementach, który na przestrzeni lat uległ znacznym modyfikacjom:
- Wymagania specyfikacji DOM4
W ramach uproszczenia platformy webowej aktualna specyfikacji DOM4 całkowicie przedefiniowuje koncepcję unikatowego identyfikator. Od tej pory elementy posiadają tylko jeden unikatowy identyfikator, który można kontrolować za pomocą HTML-owego atrybutu
id
(nie zapominając o drobnych różnicach między nimi). Dotyczy to zarówno dokumentów XML jak i dokumentów HTML.Warto podkreślić, że unikatowość identyfikatora dotyczy samego elementu, a nie wszystkich elementów w danym drzewie węzłów. Możliwe jest zatem utworzenie drzewa węzłów z wieloma elementami posiadającymi identyczny unikatowy identyfikator. Jest to jak najbardziej prawidłowe i nie powoduje zgłaszania jakichkolwiek błędów. Niemniej jednak warto unikać takiej praktyki bo zwiększa ona ryzyko późniejszego występowania trudnych do ustalenia błędów logicznych (szczególnie przy przenoszeniu elementów z takim samym unikatowym identyfikatorem).
- Wymagania poprzednich specyfikacji DOM
Wszystkie niżej wymienione rozwiązania prawdopodobnie nigdy nie zostały zaimplementowane przez jakąkolwiek przeglądarkę internetową (choć były przymiarki Mozilla - Bug 288513), a nawet jeśli część z nich wdrożono w niektórych programach, to na dzień dzisiejszy zostały z nich całkowicie usunięte. Prezentowane tutaj opisy mają charakter wyłącznie informacyjny.
Zgodnie z poprzednimi specyfikacjami DOM unikatowy identyfikator posiadały te atrybuty, które oznaczono specjalnym typem ID #. Implementacje definiowały takie atrybuty samodzielnie (jak w przypadku HTML-owego atrybutu
id
), ale mogły też udostępniać programistom inne mechanizmy zezwalające na taki manewr (np. za pośrednictwem DTD lub dedykowanych metod).Zwróćmy uwagę, że wielkość znaków w nazwach atrybutów jest istotna dla dokumentów XML, dlatego też nazwy "
id
", "Id
" lub "ID
" oznaczają różne atrybuty, chociaż można je było zdefiniować tak, żeby były typu ID. Wystarczyło utworzyć własny DTD, z którym przeglądarki prawidłowo obsługujące XML powinny sobie poradzić:<!DOCTYPE idTest [ <!ATTLIST item id ID #IMPLIED> <!ATTLIST item Id ID #IMPLIED> <!ATTLIST item ID ID #IMPLIED> ]>
W wybiórczych przypadkach można było skorzystać z odpowiedniej metody wywoływanej wprost ze skryptu, np.
Element.setIdAttribute()
,Element.setIdAttributeNS()
lubElement.setIdAttributeNode()
.Konsekwencją istnienia szerokiej dobrowolności przy definiowaniu atrybutów z typem ID był wymóg udostępnienia przez każdą implementację DOM dodatkowego polecenie informującego o tym, czy dany atrybut jest typu ID. W starszych specyfikacjach DOM odbywało się to poprzez dedykowaną właściwość
Attr.isId
.
Różnica między unikatowym identyfikatorem a samym atrybutem id
#
Trzeba zdawać sobie sprawę z tego, że atrybutu id
oraz odpowiadająca mu właściwość Element.id
wpływają jedynie na unikatowy identyfikator w danym elemencie, ale nie oznaczają tego samego. Ustawienie wartości w atrybucie id
na pusty łańcuch znakowy (dowolnym sposobem) usuwa unikatowy identyfikator w elemencie (jeśli istniał), ale wcale nie usuwa tego atrybutu z elementu. Z praktycznego punktu widzenia będzie to istotne przy wywoływaniu metody ParentNode.getElementById()
.
Prosty przykład:
<p id="">Akapit z atrybutem id="".</p>
<script>
var el = document.getElementsByTagName("p")[0];
document.write(document.getElementById("")); // null (unikatowy identyfikator nie istnieje)
document.write("<br>");
document.write(el.hasAttribute("id")); // true (ale atrybut id istnieje)
document.write("<br>");
document.write(el.getAttribute("id")); // "" - pusty łańcuch znakowy
document.write("<br>");
document.write(el.id); // "" - pusty łańcuch znakowy
document.write("<br>");
document.write(el.attributes.length); // 1
document.write("<br><br>");
el.id = "test"; // ustawiamy wartość atrybutu (co wpływa jednocześnie na unikatowy identyfikator)
document.write(document.getElementById("test")); // [object HTMLParagraphElement] (unikatowy identyfikator istnieje)
document.write("<br>");
document.write(el.hasAttribute("id")); // true
document.write("<br><br>");
el.id = ""; // ustawiamy wartość atrybutu (co wpływa jednocześnie na unikatowy identyfikator)
document.write(document.getElementById("test")); // null (unikatowy identyfikator nie istnieje)
document.write("<br>");
document.write(el.hasAttribute("id")); // true (ale atrybut id istnieje)
document.write("<br><br>");
el.id = " "; // ustawiamy wartość atrybutu (co wpływa jednocześnie na unikatowy identyfikator)
document.write(document.getElementById(" ")); // [object HTMLParagraphElement] (unikatowy identyfikator istnieje)
document.write("<br>");
document.write(el.hasAttribute("id")); // true
document.write("<br><br>");
el.removeAttribute("id"); // całkowicie usuwamy atrybut (co wpływa jednocześnie na unikatowy identyfikator)
document.write(document.getElementById("test")); // null (unikatowy identyfikator nie istnieje)
document.write("<br>");
document.write(el.hasAttribute("id")); // false (atrybut id nie istnieje)
document.write("<br>");
document.write(el.attributes.length); // 0
</script>
Dostęp do elementów za pomocą wartości z atrybutów id
oraz name
#
W platformie webowej istnieje specyficzny dostęp do niektórych elementów za pomocą wartość z atrybutów id
oraz name
bezpośrednio z obiektów typu Window
, Document
czy Form
, aczkolwiek każdy z nich ma własne (dosyć zawiłe) reguły dostępowe. Zachowanie to wciąż opisywane jest w najnowszej specyfikacji HTML5, ale wynika to tylko i wyłącznie ze względu na kompatybilność z już utworzonym kodem.
Prosty przykład:
<img id="obrazek" src="">
<img name="obrazek" src="">
<p id="akapit">Akapit z atrybutem id="akapit".</p>
<script>
document.write(akapit); // [object HTMLParagraphElement]
document.write("<br>");
document.write(this.akapit); // [object HTMLParagraphElement]
document.write("<br>");
document.write(window.akapit); // [object HTMLParagraphElement]
document.write("<br><br>");
document.write(obrazek); // [object HTMLCollection]
document.write("<br>");
document.write(this.obrazek); // [object HTMLCollection]
document.write("<br>");
document.write(window.obrazek); // [object HTMLCollection]
document.write("<br><br>");
document.write(document.akapit); // undefined
document.write("<br>");
document.write(document.obrazek); // [object HTMLImageElement]
</script>
Mimo że jest krócej (w porównaniu z innymi poleceniami DOM) to i tak nie należy stosować tej techniki, a już na pewno nie w kodzie produkcyjnym. Przeglądarki internetowe co jakiś czas wprowadzają nowe nazwy do wbudowanych interfejsów, dlatego nigdy nie będzie gwarancji, że przypadkiem nie użyjemy przyszłej nazwy, przez co utracimy dostęp do naszego elementu.
Prosty przykład:
<p id="alert">Akapit z atrybutem id="alert".</p>
<script>
document.write(alert); // function alert() { [native code] }
document.write("<br>");
document.write(this.alert); // function alert() { [native code] }
document.write("<br>");
document.write(window.alert); // function alert() { [native code] }
document.write("<br>");
document.write(document.getElementById("alert")); // [object HTMLParagraphElement]
</script>
Na domiar złego istnieją duże różnice między przeglądarkami jeśli chodzi o prawidłową interpretację omawianych mechanizmów, które w zasadzie utworzono w oparciu o działanie IE. Lepiej zawsze stosować dedykowane metody, np. ParentNode.getElementById()
lub ParentNode.querySelector()
, a powyższą informację potraktować jako ciekawostkę.
Na podobnej zasadzie działają nazwane javascriptowe odpowiedniki (getter []) dla metody NamedNodeMap.getNamedItem()
i HTMLCollection.namedItem()
, ale ich stosowanie (z tych samych powodów jak wyżej) jest niewskazane.
Atrybuty#
Specyfikacja DOM4 całkowicie przedefiniowuje pojęcie atrybutów. Od tej pory nie stanowią one osobnego rodzaju węzłów (nie dziedziczą po interfejsie Node), są po prostu obiektami z dostępem do własnych poleceń.
Na dzień dzisiejszy wszystkie aktualne przeglądarki działają jeszcze według starszego podejścia. Tak naprawdę nie wiadomo kiedy, i czy w ogóle, nowe zmiany się przyjmą. Obecnie trwa stopniowe nanoszenie zmian, ale idzie to z wielkim oporem i narastającymi wątpliwościami.
Tworzenie atrybutów#
Atrybuty to koncepcja przewidziana wyłącznie dla elementów pozwalająca doprecyzować ich specyficzne cechy. Z perspektywy DOM są one reprezentowane przez obiekty typu Attr
i w zależności od kontekstu można je utworzyć w dwojaki sposób:
- Z wykorzystaniem odpowiedniego parsera operującego na strukturze znacznikowej, gdzie atrybuty umieszcza się w znaczniku otwierającym element. Proces ten odbywa się automatycznie przy wczytywaniu dokumentu, ale nie należy zapominać o kilku poleceniach parsujących dostępnych z poziomu kodu JS.
- Z wykorzystaniem techniki odzwierciedlenia lub jednej z metod tworzących nowe atrybuty, np. poprzez wywołanie metody
Document.createAttribute()
,Document.createAttributeNS()
,Element.setAttribute()
czyElement.setAttributeNS()
. Warto podkreślić, że wspomniane metody są bardziej restrykcyjne od samego parsera HTML, a to ze względu na ścisłą weryfikację nowego atrybutu pod kątem wymagań stawianych przez specyfikację XML. Jeśli komuś faktycznie zależy na tworzeniu atrybutów z "egzotycznymi nazwami" to powinien pozostawać przy mechanizmach operujących wyłącznie na parserze HTML, i jednocześnie wystrzegać się tych, które mają coś wspólnego z parserem XML (DOM - Bug 27228).
Prosty przykład:
<script>
var newDiv = document.createElement("div");
newDiv.innerHTML = "<hr [foo]='bar'>"; // poprzez parser HTML tworzymy atrybut z nazwą niezgodną z wymogami XML
firstAttr = newDiv.firstChild.attributes[0];
document.write(firstAttr); // [object Attr]
document.write("<br>");
document.write(firstAttr.name); // [foo]
document.write("<br><br>");
try{
newDiv.setAttribute("[foo]", "bar"); // tworzymy atrybut z nazwą niezgodną z wymogami XML
}
catch(e){
document.write("Przekazanie do metody setAttribute() argumentu niezgodnego z wymogami XML zrzuca błąd:" + "<br>");
document.write(e); // opis zależny od przeglądarki
document.write("<br>");
document.write(e.constructor); // function DOMException() { [native code] }
}
document.write("<br><br>");
try{
var newAttr = document.createAttribute("[foo]"); // tworzymy atrybut z nazwą niezgodną z wymogami XML
}
catch(e){
document.write("Przekazanie do metody createAttribute() argumentu niezgodnego z wymogami XML zrzuca błąd:" + "<br>");
document.write(e); // opis zależny od przeglądarki
document.write("<br>");
document.write(e.constructor); // function DOMException() { [native code] }
}
</script>
Atrybuty bez skojarzonego elementu#
W ujęciu praktycznym atrybuty mogą istnieć jako samodzielne obiekty, bez skojarzenia z jakimkolwiek elementem, ale można to osiągnąć jedynie po stronie kodu JS, np. tworząc nowy atrybut za pomocą metody Document.createAttribute()
lub usuwając go z powiązanego elementu za pośrednictwem odpowiedniej metody. Istnienie powiązania atrybutu z jakimś elementem można ustalić poprzez właściwość Attr.ownerElement
. Fakt skojarzenia jest istotny przy ustawianiu atrybutu (postać obiektu) w danym elemencie, bo operacja taka jest niedozwolona w przypadku atrybutów, które skojarzone zostały z innym elementem niż ten, na którym jakaś ustawiająca metoda została wywołana.
Prosty przykład:
<script>
var html = document.documentElement; // referencja do elementu HTML
html.setAttributeNS("www.test.com", "p:AAA", "Test"); // ustawiamy nowy atrybut
var firstAttr = html.attributes[0]; // referencja do pierwszego atrybutu z listy
document.write(html.attributes[0].ownerElement); // [object HTMLHtmlElement]
document.write("<br>");
document.write(html.attributes[0].ownerElement == html); // true
document.write("<br><br>");
var newAttr = document.createAttribute("test"); // nowy atrybut bez skojarzenia z jakimkolwiek elementem
document.write(newAttr.ownerElement); // null
html.setAttributeNode(newAttr); // ustawiamy nowy atrybut dla elementu HTML
document.write("<br>");
document.write(newAttr.ownerElement); // [object HTMLHtmlElement]
document.write("<br><br>");
try{
document.body.setAttributeNode(newAttr); // ustawiamy atrybut skojarzony z innym elementem niż HTML
}
catch(e){
document.write("Przekazanie do metody setAttributeNode() atrybutu skojarzonego z innym elementem niż HTML zrzuca błąd:" + "<br>");
document.write(e); // opis zależny od przeglądarki
document.write("<br>");
document.write(e.constructor); // function DOMException() { [native code] }
}
</script>
Na chwilę obecną nie jest to szczególnie interesujące, mało kto operuje bezpośrednio na obiektach atrybutów i w codziennej pracy najczęściej korzysta się z wygodniejszych poleceń wywoływanych na jakimś elemencie.
Dziedziczenie z Node#
Zgodnie z wcześniejszymi specyfikacjami DOM interfejs Attr dziedziczył po interfejsie Node, dlatego atrybuty miały dostęp do wszystkich ogólnych poleceń węzłowych. Problem w tym, że atrybuty nie są dziećmi węzłowymi dla swoich elementów (innych węzłów), dlatego też nigdy nie uważane były za część drzewa węzłów. Efektem tego jest to, że niektóre polecenia z interfejsu Node nie mają żadnego zastosowania w przypadku atrybutów i najczęściej zwracają wartość null
.
Prosty przykład:
<script>
var html = document.documentElement; // referencja do elementu html
html.setAttributeNS("www.test.com", "p:AAA", "Test"); // ustawiamy nowy atrybut
var allAttr = html.attributes; // pobieramy listę atrybutów
var firstAttr = allAttr[0]; // pierwszy atrybut z listy
document.write(firstAttr); // [object Attr]
document.write("<br>");
document.write(firstAttr.value); // Test
document.write("<br>");
document.write(firstAttr.nodeValue); // Test
document.write("<br>");
document.write(firstAttr.textContent); // Test
document.write("<br>");
document.write(firstAttr.nodeType); // 2
document.write("<br>");
document.write(firstAttr.nodeName); // p:AAA
document.write("<br><br>");
document.write(firstAttr.childNodes); // [object NodeList]
document.write("<br>");
document.write(firstAttr.childNodes.length); // 0
document.write("<br>");
document.write(firstAttr.childNodes[0]); // undefined
document.write("<br>");
document.write(firstAttr.firstChild); // null
document.write("<br>");
document.write(firstAttr.lastChild); // null
document.write("<br><br>");
document.write(firstAttr.nextSibling); // null
document.write("<br>");
document.write(firstAttr.previousSibling); // null
document.write("<br>");
document.write(firstAttr.parentNode); // null
document.write("<br><br>");
for (var prop in firstAttr) {
document.write(prop + "<br>"); // zwracamy wszystkie wyliczalne właściwości z atrybutu
}
</script>
Zrezygnowanie z dziedziczenia poleceń węzłowych przez atrybuty pozwala w znacznym stopniu uprościć całą specyfikację. Prawdę mówiąc, gdyby dzisiaj przyszło definiować interfejs Attr ponownie, to miałby on tylko dwie właściwości: Attr.name
i Attr.value
. Niestety zachowanie kompatybilności wstecznej wymusza dodawanie kolejnych starszych/niepotrzebnych poleceń dotyczących atrybutów, które występują także w innych interfejsach, i w niektórych przypadkach mogą nawet powielać to samo zachowanie. Część z nich ma mylące nazwy, które mogą sugerować (już po wykonaniu wszystkich zmian), że atrybuty wciąż mają coś wspólnego z węzłami:
Attr.nodeValue
Element.setAttributeNode()
Element.setAttributeNodeNS()
Element.getAttributeNode()
Element.getAttributeNodeNS()
Element.removeAttributeNode()
- NamedNodeMap
Szerszą analizę powodów pozostawienia starych poleceń atrybutowych przedstawił Anne van Kesteren we wpisie "DOM: attributes sadness".
Większym problemem będą aktualne implementacje wszystkich poleceń związanych z atrybutami. Na dzień dzisiejszy, po wielu przebojach i zgłoszonych błędach, najwłaściwiej zachowuje się jedynie Firefox, i wydaje się, że cała specyfikacja DOM4 odzwierciedla zachowanie Firefoksa w tym obszarze. Chrome ma kilka uchybień (głównie związanych z algorytmem pobrania atrybut poprzez nazwę), a IE ma tyle błędów, że ciężko się w ogóle do tego odnosić.
Na przestrzeni kilku lat praca z atrybutami uległa znacznemu uproszczeniu. Wszystko co związane z przestrzeniami nazw w atrybutach stało się zbędne, dlatego w codziennej pracy proponuję stosować jedynie następujące rozwiązania: #
- odzwierciedlenie
Element.getAttribute()
Element.setAttribute()
Element.hasAttribute()
Element.removeAttribute()