Właściwości elementów

Jeżeli pobierzesz jakikolwiek element ze strony, będziesz mógł w konsoli wyświetlić jego właściwości i metody.
Najlepiej od razu odpal debuggera (F12) i zostaw konsole widoczną, bo jeszcze nam się przyda.

Pobrałem za ciebie powyższy przycisk. Otwórz debugger i rozwiń wypisany element.


const btn = document.querySelector(".button");
console.dir( btn );

Powyższy screen to tylko urywek całości.
Obiekt taki ma bardzo dużo właściwości i metod. Część z nich przechowuje pozycję na stronie, typ elementu, jego rozmiary, inne są wykorzystywane dla zdarzeń itp.
Nie wszystkie będziesz non stop używał. Jeżeli trafi się taka konieczność, wiedz po prostu gdzie szukać tego co cię interesuje (rozwijając pobrany element i wypisując go w konsoli).

Poniżej skupimy się na właściwościach i metodach, które są wykorzystywane najczęściej.

innerHTMLzwraca lub ustawia kod HTML danego element
outerHTMLzwraca lub ustawia kod HTML wraz z tagiem
innerTextzwraca lub ustawia tekst znajdujący się w elemencie (bez html)
tagNamezwraca nazwę tagu
getAttributepobiera atrybut elementu
setAttributeustawia atrybut elementu
hasAttributesprawdza czy element ma dany atrybut
toggleAttributedodaje lub usuwa dany atrybut
datasetzwraca (obiekt) dataset, który przetrzymuje customowe atrybuty (data-...).

innerHTML i outerHTML

Właściwość innerHTML umożliwia odczyt i ustawianie html, jaki jest we wnętrzu danego elementu:


const btn = document.querySelector(".btn");

console.log( btn.innerHTML ); //<span>Kliknij mnie!</span>

btn.innerHTML = "<span>Nie klikaj mnie!</span>"

Za pomocą innerHTML możemy wstawiać całe kawałki html dynamicznie:


<div class="test-cnt">Tutaj zaraz coś wstawimy</div>

<button class="button" type="button">Stwórz dynamicznie html</button>

const btn = document.querySelector(".button");

btn.addEventListener("click", () => {
    document.querySelector(".test-cnt").innerHTML = `
        <div class="module">
            <p>To jest <strong>dynamicznie wstawiony html</strong>:</p>
            <button class="button">Klik!</button>
            <button class="button">Inny klik!</button>
        </div>
    `;
});
Tutaj zaraz coś wstawimy

Podobną do innerHTML jest właściwość outerHTML, z tym, że zwraca także kod html samego elementu:


const btn = document.querySelector(".btn");
console.log(btn.outerHTML); //<button class="button" type="button"><span>Kliknij mnie</span></button>

innerText i textContent

Właściwości innerText i textContent działają podobnie do powyższej innerHTML, z tym, że zwracają lub ustawiają sam tekst:


const btn = document.querySelector(".btn");

console.log(btn.innerHTML); //<span>Kliknij mnie</span>
console.log(btn.innerText); ///"Kliknij mnie"
console.log(btn.textContent); //"Kliknij mnie"

Podstawowa różnica między innerText a textContent jest taka, że ta pierwsza zwraca tekst po zaaplikowaniu stylów, a textContent je pomija.

Co to oznacza? Jeżeli na jakiś element w tekście zostanie poprzez style nadaje visibility:hidden lub display:none, wtedy innerText nie zwróci ukrytych części:


.button-text-none {
    display:none;
}

.button-text-hide {
    visibility:hidden;
}

<div class="test">
    <span class="button-text-none">Ukryty</span>
    <span>Obok mnie są dwa niewidoczne spany</span>
    <span class="button-text-hide">Niewidoczny</span>
</div>

<button class="button" type="button">
    Kliknij
</button>

const btn = document.querySelector(".button");
const divTest = document.querySelector(".test");

btn.addEventListener("click", () => {
    console.log("innerHTML: ", divTest.innerHTML);
    console.log("innerText: ", divTest.innerText);
    console.log("textContent: ", divTest.textContent);
});
Ukryty Obok mnie są dwa niewidoczne spany Niewidoczny

Dodatkowo jeżeli wewnątrz tekstu są znaczniki <br>, wtedy innerText zwróci tekst z nową linią tam gdzie te znaczniki występują:


<p>To jest tekst <br>złamany</p>

const p = document.querySelector("p");

console.log(p.innerText);
//To jest tekst
//złamany

console.log(p.textContent);
//To jest tekst złamany

Z racji tego, że textContent nie sprawdza czy dane znaczniki są ukryte czy też nie, jest też nieco szybszy od swego brata. A to akurat tylko ciekawostka, bo w realnych aplikacjach takie różnice totalnie nie mają znaczenia...

tagName

Właściwość tagName zwraca nam nazwę elementu np. h2, a, p itp.


const btn = document.querySelector(".button");
console.log(btn.tagName);

Do czego to może się przydać? Przykładowo można pobrać wiele elementów na raz, a następnie tylko dla określonych coś zmieniać:


const elements = document.querySelector("body *");

for (const el of elements) {
    if (el.tagName === "strong") {
        console.log(el.innerText);
        el.style.border = "1px solid red";
    }
}

W powyższym skrypcie pobraną nazwę znacznika dodatkowo zamieniłem na małe litery. Dla elementów pobranych z HTML nie trzeba tego robić bo nazwy znaczników zwracane są dużymi literami. To dodatkowe zabezpieczenie może nam jednak pomóc gdy nasz skrypt używamy dla dokumentów w innym formacie - np. xhtml.

Możesz się o tym przekonać. Wejdź na stronę http://www.powiatzaganski.pl/ i otwórz debuggera. Wpisz w konsoli document.body.tagName. Dodatkowo otwórz zakładkę Network, odśwież stronę i sprawdź pierwszy z góry request (www.powiatzaganski.pl) - a dokładniej przyjrzyj się nagłówkowi contentType.

tagName

Praca z atrybutami

Do pracy z atrybutami danego elementu (np. src dla img) możemy skorzystać z poniższych metod:

el.getAttribute(name) pobiera wartość danego atrybutu lub zwraca null jeżeli takiego nie ma
el.setAttribute(name, value) ustawia nową wartość atrybutu
el.hasAttribute(name) zwraca true/false w zależności czy element ma atrybut o danej nazwie
el.removeAttribute(name) usuwa atrybut o danej nazwie
el.toggleAttribute(name) dodaje/usuwa dany atrybut

<a href="http://google.pl"> Wyszukaj </a>

const link = document.querySelector("a");

const href = link.getAttribute("href"); //"http://google.pl"
const target = link.getAttribute("target"); //null

link.setAttribute("target", "_blank");
if (link.hasAttribute("target")) {
    console.log(link.getAttribute("target")); //"_blank"
}

const btn = document.querySelector("button");
const input = document.querySelector("input");

btn.addEventListener("click", e => {
    input.toggleAttribute("readonly");

    if (input.hasAttribute("readonly")) {
        btn.innerText = "zakończ";
    } else {
        btn.innerText = "edytuj";
    }
})

dataset

Atrybuty możemy podzielić na standardowe dostępne dla danych znaczników (np. src, alt, tooltip, class itp.), oraz nasze własne. Te drugie powinny zaczynać się od słowa data- np. data-text, data-direction.

Atrybuty takie możemy obsługiwać za pomocą powyższych metod tak samo jak pozostałe atrybuty, ale możemy dla nich skorzystać z właściwości dataset. Jest to obiekt, którego kolejne właściwości są budowane na bazie niestandardowych atrybutów:


<div class="module"
    data-type="important"
    data-position="top"
    data-my-custom-data="Przykładowy tekst"
> ... </div>

const tooltip = document.querySelector(".module");

console.log(tooltip.dataset.type); //"important"
console.log(tooltip.dataset.position); //"top"
console.log(tooltip.dataset.myCustomData); //"Przykładowy tekst"

Zauważ, jak odwołaliśmy się do nazw naszych parametrów.
Początek data- został pominięty, a zapis my-custom-data zmieniliśmy na pisany camelCase czyli myCustomData. Z tego też powodu nazwy niestandardowych atrybutów nie powinny zawierać ani spacji, ani dużych liter.

Jeżeli teraz chcielibyśmy ustawić jakiś niestandardowy atrybut, lub zmienić istniejący, wystarczy, że ustawimy im nową wartość:


tooltip.dataset.myCustomData = "nowa wartość"; //utworzy w elemencie atrybut data-my-custom-data="nowa wartość"
tooltip.dataset.style = "primary"; //utworzy atrybut data-style="primary"
tooltip.dataset.moduleSize = "big" //utworzy atrybut data-module-size="big"

//to samo możemy uzyskać za pomocą getAttribute i setAttribute
tooltip.setAttribute("data-custom-data", "nowa wartość");
tooltip.setAttribute("data-style", "primary");
tooltip.setAttribute("data-module-size", "big");

Charakterystyczną rzeczą jest to, że atrybuty w znacznikach html zawsze są stringami. Tak więc cokolwiek byśmy nie przetrzymywali w dataset, staje się to tekstem:


<div data-nr1="16" data-nr2="30"> ... </div>

console.log(typeof div.dataset.nr1, typeof div.dataset.nr2) //"string", "string"
console.log(div.dataset.nr1 + div.dataset.nr2); //"1630"

Niestandardowe atrybuty idealnie nadają się więc do przechowywania tekstów dynamicznie wstawianych na stronę, kawałków html, które zaraz wmontujemy w stronę, czy dodatkowych wartości służących do stylowania za pomocą atrybutów.


div.dataset.type = "error";

div[data-type="error"] {
    color: red;
}

Przykładem (jednym z setek) mogą być tooltipy w popularnym frameworku Bootstrap. Dzięki dodatkowych atrybutom określana jest tutaj pozycja dymka.

bootstrap tooltipy

Przy liczbach także nie powinno być większych problemów, ponieważ łatwo możemy je konwertować:


console.log( Number(div.dataset.nr1) + Number(div.dataset.nr2) ); //46

Problem pojawia się, gdy w dataset chcemy przetrzymywać inne typy danych - np. tablice i obiekty:


div.dataset.table = [1,2,3,4,5];
div.dataset.ob = {name : "Marcin"};

console.log(div.dataset.table); //"1,2,3,4,5"
console.log(div.dataset.ob); //"[object Object]"
dataset

Aby przetrzymywać takie rzeczy w atrybutach, możemy posłużyć się obiektem JSON, który udostępnia nam 2 metody: stringify() - do konwersji obiektu na tekstowy zapis, oraz parse() - do odkodowania takiego zapisu:


div.dataset.table = JSON.stringify( [1,2,3,4,5] );
div.dataset.ob = JSON.stringify( {name : "Marcin"} );

console.log( JSON.parse(div.dataset.table) ); //["1,2,3,4,5"]
console.log( JSON.parse(div.dataset.ob) ); //{name : "Marcin"}
dataset

Przy czym jak przeczytasz tutaj, format JSON służy tylko do przetrzymywania danych, co oznacza, że nie da się tutaj przetrzymywać metod obiektu.


const ob = {
    name : "Karol",
    print() {
        console.log(this.name);
    }
}

document.body.dataset.ob = JSON.stringify( ob );
console.log(document.body.dataset.ob); //{"name":"Karol"}

Jeżeli zajdzie konieczność przechowania obiektu z metodami, dataset nie będzie tym w co chcemy celować.

Atrybuty kontra właściwości

Dzięki DOM możemy pobierać, manipulować i działać na elementach HTML. HTML to tak naprawdę kawałek tekstu, który napisaliśmy w edytorze. Javascript analizuje taki dokument i dla każdego elementu w HTML tworzy jego odzwierciedlenie w postaci odpowiedniego obiektu:


<a id="link" href="https://google.pl"> było - zamykam temat </a>

const link = document.querySelector("#link");
console.dir(link);

Obiekt taki automatycznie dostał wiele właściwości. Wśród nich są te, które odpowiadają atrybutom z html - np. id, href ale też takie które element danego typu potencjalnie może mieć. W powyższym przykładzie mamy link, więc ma on dla przykładu atrybut target. Nasz obiekt w JS także ma taką właściwość.

html i js

<img src="obrazek.jpg" alt="obrazek" title="Fajny obrazek">

const img = document.querySelector("img");

img.src //"obrazek.jpg"
img.alt //"obrazek"
img.title //"Fajny obrazek"

Gdy JavaScript tworzy taki obiekt, niektóre wartości są dodatkowo przeliczane:


<a id="link1" href="./kontakt.html"> kontakt </a>
<a id="link2" href="https://google.pl"> było - zamykam temat </a>

const link1 = document.querySelector("#link1");
console.log( link1.href ); //file:///C:/Users/kartofelek/Desktop/kontakt.html

const link2 = document.querySelector("#link1");
console.log( link2.href ); //https://google.pl

Widzisz jak wygląda wartość dla pierwszego odnośnika? To link do relatywnej strony na moim dysku, ponieważ nie zaczyna się od żadnego protokołu (np. http, https). JavaScript tworząc dla nas odpowiednik naszego elementu, wyliczył sobie jego realną właściwość href.

Atrybuty i właściwości są ze sobą zmapowane (są scalone). Oznacza to, że przykładowo zmieniając atrybut href, zmienimy też właściwość href i vice wersa.


<a id="link" href="https://google.pl"> było - zamykam temat </a>

const a = document.querySelector("a");

a.href = "strona.html"
console.log(a.getAttribute("href")); //strona.html
console.log(a.href); //file:///C:/Users/kartofelek/Desktop/test/strona.html

const a = document.querySelector("a");

a.setAttribute("href", "strona.html"); //strona.html
console.log(a.getAttribute("href")); //file:///C:/Users/kartofelek/Desktop/test/strona.html
console.log(a.href);

Badając taki element w zakładce Elements debugera zobaczymy, że zmieniając wartość właściwości href wartość atrybutu też się zmieni.

Podobnie będzie z innymi właściwościami, które mają swój odpowiednik w html:


<img src="obrazek.jpg" alt="obrazek" title="Fajny obrazek">

const img = document.querySelector("img");
img.title = "Wcale nie fajny";
console.log(img.getAttribute("title")); //"Wcale nie fajny"

Tu pojawia się też pewna ciekawostka.

Dla inputów w formularzach istnieją dwie właściwości - defaultValue i value.

Wchodząc na stronę z formularzem, część pól może być już domyślnie wypełniona. Mają one wtedy ustawiony atrybuty value np:


<input type="text" value="Piotrek">

Atrybut ten jest zmapowany z właściwością defaultValue, co oznacza, że jego wartość możemy odczytywać ale i zmienić na dwa sposoby:


input.setAttribute("value", "nowa-wartość");
input.defaultValue = "nowa-wartość"

Wartość value natomiast zawiera zawsze aktualną wartość pola. Po wejściu na stronę wartość ta będzie identyczna jak defaultValue. Gdy jednak użytkownik zacznie ręcznie edytować dane pole, właściwość value automatycznie zaktualizuje swoją wartość, natomiast atrybut (a co za tym idzie defaultValue) pozostanie bez zmian.

Dla nas oznacza to tyle, że jeżeli chcemy odczytać początkową wartość pola - użyjmy defaultValue. Natomiast jeżeli chcemy pobrać aktualną wartość pola - value. Podobna sytuacja będzie się dziać w przypadku checkboxów i radio gdzie mamy właściwości defaultChecked i checked.

Dla bardzo dociekliwych mechanizm ten opisany jest w dokumentacji.

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-dom

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem. Aha - i ta strona korzysta z ciasteczek.

Menu