View on GitHub

info

Материјали за курс из УВИТ-а на Математичком факултету Универзитета у Београду

УВИТ - ЈаваСкрипт програмирање на клијентској страни

Владимир Филиповић

Увод

Овде се разматра коришћење језика ЈаваСкрипт за веб програмирање на клијентској страни. Наиме, приликом довлачења веб странице, довлачи се и смешта у прегледач ЈаваСкрипт код који се извршава. Приликом приказа веб странице се извршава у прегледачу претходно довучени ЈаваСкрипт код, који приликом извршавања интерагује са елементима на веб страни и динамички их мења.

ЈаваСкрипт и веб стране

Из клијентског ЈаваСкрипт-а се може програмски приступити елементима на веб страни, читати их, мењати њихове атрибуте, додавати их или брисати, по потреби.

Објекти ЈаваСкрипт окружења код веб прегледача

Свака веб страна омогућује да се из ЈаваСкрипт кода приступа следећим објектима:

Пример. Илустрација постављања својстава за објекте веб стране:

document.title = 'Probni dokument'; //naziv dokumenta (title)
document.bgColor = '#070707';
document.fgColor = '#00FFFF';

history.length = 7;

location.href = 'http://uvit.math.rs';

Дијаграм који следи описује хијеархијску структуру објеката ЈаваСкрипт окружења код веб прегледача:

Хијерархија DOM објеката

Допуштен је приступ елементима на веб страни из ЈаваСкрипт-а. Један начин за програмски приступ елементима на веб страни је реферисање преко вредности атрибута name.

Пример. У програмском коду који следи, елементима на веб страни се може приступити на основу имена, додeљеног као одговарајући атрибут веб стране:

let forma = document.mojaForma;
let txt = forma.textIme;
console.log(txt.outerHTML);
let btn = forma.buttonOk;
console.log(btn.outerHTML);

ЈаваСкрипт објекти који су дотупни преко ваб прегледача могу имати своје атрибуте.

Пример. У програмском коду који следи, постављамо атрибуте ЈаваСкрипт објекте који се односе на елементе са веб стране, а чије су вредности одређене у претходном примеру.

<!DOCTYPE html>
<html>
    <head>
        <script>
            function podesi(){
                console.log(document.mojaforma);
                document.mojaforma.action = "http://uvit.matf.bg.ac.rs/primeri/obrada.php";
                document.mojaforma.method = "GET";
                document.mojaforma.length = 5;
                document.mojaforma.button1.value = "Klikni ovde";
                document.mojaforma.button1.name = "taster1";
                document.mojaforma.text1.value = "sadržaj tekst polja";
                document.mojaforma.text1.name = "TekstPolje1";
                document.mojaforma.check1.defaultChecked = true;
                document.mojaforma.check1.value = "on";
                document.mojaforma.check1.name = "CheckPolje1";
            }
        </script>
    </head>
    <body onload="podesi()">
        <form name="mojaforma">
            <input name="button1" type="submit">
            <input name="text1" type="text">
            <input name="check1" type="checkbox">
        </form>
    </body>
</html>

Објектни модел документа (DOM)

Објектни модел документа (eng. Document Object Model - DOM) је програмски интерфејс (АПИ) којим се ЈаваСкрипт програмима омогућава да комуницирају са HTML документима и њиховим елементима.

Основни DOM објекат је HTML документ чијa je структурa верно пресликана у (објектно-оријентисан) АПИ.

DOM објекат за дати елемент на веб страни има структуру дрвета.

Пример. За следећи исечак HTML документа, који предстаља табелу:

<table>
  <tbody>
    <tr>
      <td>Shady Grove</td>
      <td>Aeolian</td>
    </tr>
    <tr>
      <td>Over the River, Charlie</td>
      <td>Dorian</td>
    </tr>
  </tbody>
</table>

одговарајући DOM објекат има следећу структуру:

DOM објекат за табелу

DOM објекат за дату веб страну такоше има структуру дрвета, где се сарджавање једног елемента у другом представља односм родитељ-потомак.

Пример. За следећи HTML документ:

<html>
  <head>
    <title>Moja stranica</title>
  </head>
  <body>
    <p id="p1">Prvi paragraf</p>
    <p class="c1">Drugi paragraf</p>
  </body>
</html>

добијају се следећи DOM објекти:

То се прегледно може илустровати следећом сликом:

DOM објекат за веб страну

Операције са DOM објекатима

Са DOM-ом, ЈаваСкрипт има могућност да креира динамичке HTML стране:

Објектима који су део документа може се приступити на неколико начина, а најчешће коришћени су:

  1. Преко идентификатора (id атрибута) - добија се један елемент. Пример. Приступ првом пасусу са претходне веб стране: document.getElementById("p1")
  2. Преко назива елемента (назива етикете) - добија се коликција елемената. Пример. Приступ свим пасусима са претходне веб стране: document.getElementsByTagName("p")
  3. Преко класе којој елементи припадају - добија се колекција елемената. Пример. Приступ другом пасусу са претходне веб стране: document.getElementsByClassName("c1")
  4. Преко позиције у дрвету, коришћењем метода parentNode, nextSibling, prevSibling, firstChild, lastChild, као што је приказано на следећој слици: Приступ чворовима DOM-а
  5. Преко css селектора, коришћењем метода querySelecor i querySelectorAll - што подржавају сви модерни веб прегледачи: Пример. Претходно описани приступи елементима са претходне веб стране могу да се реализују и на следeћи начин:
document.querySelector("#p1");   // pristup prvom paragrafu iz gornjeg primera
document.querySelector(".c1");   // pristup prvom elementu klase c1
document.querySelectorAll("p");  // pristup svim paragrafima gornjeg primera

Догађаји код DOM објеката

Догађаји код DOM објеката омогућавају ЈаваСкрипт-у да региструје појаву неке промене у документу. За сваку такву промену ЈаваСкрипт може да дефинише функцију која ће је опслужити. Функције које опслужују догађаје називају се руковаци (енг. handler).

DOM догађаји могу се сврстати у неколико категорија:

Стандардан начин за регистровање и опслуживање DOM догађаја је додавање ослушкивача догађаја (енг. event listener).

Пример. Веб страна са дугметом, где се приликом клика миша на дугме регистује којим је дугметом (лево средње десно) извршен клик, а потом се на конзоли приказује врста дугмета миша и координате показивача миша (на у контексту стране, а не дугмета) у тренутку клика:

<!doctype html>
<html>

<head>
    <title>Osluskivac misa</title>
</head>

<body>
  <button>Pritisni me kako god mislis da je najpametnije</button>
<script>
  let button = document.querySelector("button");
  button.addEventListener("mousedown", function(event) {
    if (event.which == 1)
      console.log("Left button");
    else if (event.which == 2)
      console.log("Middle button");
    else if (event.which == 3)
      console.log("Right button");
    console.log(`(x,y):(${event.pageX},${event.pageY})`);
  });
</script>
</body>
</html>

Функције за приказ ниске као HTML

Следеће методе враћају ниску обавијену са HTML тагом (евентуално неким HTML атрибутом):

Назив методе Опис
anchor() враће елеменат сидро
big() враће елеменат који садржи ниску са повећаним словима
blink() враће елеменат који садржи трепћућу ниску
bold() враће елеменат који садржи ниску са подебљанимм словима
fixed() враће елеменат који садржи ниску са словима фикне величине
fontcolor() враће елеменат који садржи ниску са словима у датој боји
fontsize() враће елеменат који садржи ниску са словима дате величине
italics() враће елеменат који садржи ниску са искошеним словима
link() враће елеменат хипервезу који садржи дату ниску
small() враће елеменат који садржи ниску са умањеним словима
strike() враће елеменат који садржи ниску са прекрижаним словима
sub() враће елеменат који садржи ниску са словима у индексу
sup() враће елеменат који садржи ниску са словима у експоненту

AJAX

AJAX (енг. Asynchronous Javascript And XML) представља скуп техника преко којих је могуће успоставити асинхрону комуникацију између клијента и сервера, или једноставније, у питању је механизам који омогућава аутоматско ажурирање садржаја делова странице без потребе за поновним учитавањем.

Другим речима, коришћењам AJAX-а, веб апликације могу да шаљу и примају податке са сервера асинхроно (у позадини) без мењања тренутног приказа и понашања странице. Подаци могу бити преузети помоћу објекта XMLHttpRequest. Без обзира на име, коришћење XМЛ-а није обавезно, већ се се често користи JSON уместо XML, а захтеви не морају бити асинхрони.

AJAX није једна, већ група технологија. HTML и CSS се могу користити за обележавање и стилизовање информација, ЈаваСкрипт приступ преко DOM-а обезбеђује динамички приказ и омогућује интеракције корисника са информацијом, а ЈаваСкрипт и објекат XMLHttpRequest омогућују метод за асинхрону размену података између веб прегледача и сервера да би се избегло поновно учитавање целе странице.

Синхрони модел комуникације

Синхрони модел комуникације између клијента и сервера подразумева следеће кораке:

Горе описани сценарио функционише ако је захтев уредно послат, примљен и обрађен. У случају грешака у обради захтева сервер ће (уместо HTML-а и других тражених садржаја) послати клијенту одговарајући статусни код. Поред HTML-а, сервер ће (на захтев клијента) послати и сав припадајући садржај: CSS, ЈаваСкрипт, као и друге датотеке (најчешће слике).

Јасно је да у овом сценарију нема аутоматског учитавања, што доводи до следећих последица:

Другим речима, наступа “сецкање”, ресетовање и потреба за ручним учитавањем порука - што никако није добро.

АЈАX захтеви - слање и обрада

Специјализовани XMLHttpRequest објекти размењују податке са сервером на следећи начин:

Пример. Целокупан пример (серверски и клијентски део) где клијент користи XMLHttpRequest објекат за слање АЈАX позива и обраду одговора сервера.

Серверски део је рализован у датотеци veb-server.js коришћењем node.js. Он за дати захтев као одговор враће текстулану датотеку која је специфицирана у оквиру упита.

let http = require('http');
let url = require('url');
let querystring = require('querystring');
let fs = require('fs');

http.createServer(function (request, response) {
    pathName = url.parse(request.url).pathname;
    fs.readFile(__dirname + pathName, function (err, data) {
        if (err) {
            response.writeHead(404, { 'Content-type': 'text/plan' });
            response.write(`Page Was Not Found 
            ${JSON.stringify(err)}`);
            response.end();
        } else {
            response.writeHead(200);
            response.write(data);
            response.end();
        }
    });
}).listen(7000);

Веб страна која користи AJAX има следећи облик. Битна напомена: наредну страницу је потребно покренути из http окружења, а не из система датотека, тј. адреса која је приказана на прегледачу не треба да почиње са апсолутном путањом до датотеке, већ са http. Да би се ово постигло, може се искористити већ постојећи сервер који свакако опслужује статичке датотеке. Дакле, ако је следећа датотека нпр. именована “test.html”, онда најпре у веб прегледачу уписати адресу http:localhost:7000/test.html, а након тога користити страницу како би се помоћу AJAX-а дошло до траженог ресурса асинхроно.

<!doctype html>
<html>

<head>
    <title>AJAX demo</title>
    <script type="text/javascript">
        function loadXMLDoc() {
            let fileName = document.getElementById("fileName").value;
            let xmlhttp = new XMLHttpRequest();
            xmlhttp.onreadystatechange = function() {
                if (xmlhttp.readyState == 4 &&
                    xmlhttp.status == 200) {
                    document.getElementById("myDiv").innerHTML = xmlhttp.responseText;
                    console.log("Spoljašnji sadržaj je uspešno učitan!");
                } else
                if (xmlhttp.status != 200 && xmlhttp.status != 0)
                    console.log("Greška: " + xmlhttp.status + ": " + xmlhttp.statusText);
            }
            xmlhttp.open("GET", "http://localhost:7000/"+fileName, true);
            xmlhttp.send();
        }
    </script>
</head>
<body>
    <h2>Odgovor dobijen putem AJAX-a</h2>
    <div id="myDiv"></div>
    <input id="fileName" type="text" value="ajax.txt">
    <button type="button" onclick="loadXMLDoc()"> Pošalji AJAX zahtev</button>
</body>
</html>

HTML компоненте

HTML компоненте представљају скуп стандарда или технологија који омогућавају креирање HTML елемента и њихово коришћење у оквиру веб страница и апликација. HTML компоненте јесу део W3C спецификације што их чини стандардом за имплементацију нових елемената.

Када треба убацити нову функционалност у веб апликацију, при чему се та функционалност обухвата више типова ресурса и сваки од тих ресурса треба повезати са главним документом на коме се приказује нова функционалност, поступак је обично следећи:

Коришћењем HTML компоненти имплементација се значајно упрошћава и своди на само два корака:

HTML компоненте се ослањају на следеће технологије:

Прилагођени HTML елементи

Слично предефинисаним HTML елементима (као што су <video>, <select>, <footer> итд.) сада постоји могућност да се креирају сопствени нови елементи, тзв. прилагођени елементи (енг. custom elements) који касније могу бити коришћени и на другим пројектима.

Прилагођеним елементима може да се дефинише назив, атрибути и садржај, чиме се у ствари врши комуникација сакомуницирамо са АПИ-јем елемента.

Приликом дефинисања новог елемента пожељно је да се користи цртица тј. минус у називу, како би се смањила могућност преклапања имена са именима других елемената. Такође, пожељно је да назив прилагошеног елемента и његових атрибута одражава њихову намену и смисао.

Пример. Развити прилагођени елемент <uvit-student> и веб страну која ће садржавати овакве елементе.

У овом случају, донесена је одлука да прилагођени елеменат буде дефинисан у посебној датотеци student-component.js, одвојено од веб стране:

class Student extends HTMLElement {
    constructor() {
        // увек у конструктору на почетку позвати super()
        super();
    }

    connectedCallback() {
        this.innerHTML =
            `<h2>
                Ovo je student (connected Callback)!
            </h2>`;
    }

    disconnectedCallback() {}

    attributeChangedCallback(attrName, oldVal, newVal) {}
}
// нови елеменат
let StudentComponent = customElements.define('uvit-student', Student);

Уочава се да прилагођени елементи наслеђују класу HTMLElement. У овом примеру, постављањем атрибута innerHTML одређује се садржина елемента, и то нје реализовано у контруктору, већ у телу функције connectedCallback која се извршава по успешном повезиванњу елемента у страни. Последњом наредбом доделе је реализовано повезивање класе прилагођеног елемента са етикетом <uvit-student>.

Веб страна која садржи овако направљене прилагођене елементе има следећи облик:

<!DOCTYPE html>
<html lang="sr">

<head>
    <title>Nove HTML komponente</title>
    <meta charset="UTF-8" />
    <script src='student-component.js'></script>
    <style type="text/css">
        uvit-student {
            color:red;    
        }
    </style>
</head>

<body>
    <h1>Илустрација нових <code>HTML</code> компоненти</h1>
    <script>
        document.body.appendChild(new StudentComponent());
    </script>
    <p>
        Статичан садржај веб стране...
    </p>
    <uvit-student></uvit-student>
    <uvit-student style="color:violet"></uvit-student>

</body>

</html>

Уочавамо да се укључивање нове врсте елемента постиже помоћу <script> етикете. У овом случају, изглед прилагођеног елемнта тј. HTML компонетне је дат у оквиру саме веб стране, што није најелегантније решење.

Садржај који ће бити приказан приликом исцртавања ова два <uvit-student> елемента је дефинисан у ЈаваСкрипт датотеци.

Приликом приказа у прегледачу, горња веб страна ће имати следећи изглед:

Прилагођени HTML елементи

HTML компоненте и Shadow DOM

Shadow DOM омогућава енкапсулацију ЈаваСкрипта, стилова и шаблона у HTML компоненти тако да они остају одвојени од главног ДОМ-а станице. На тај начин дизајнер HTML компоненте добија могућност да одлучи којим деловима компоненте ће крајњи корисник моћи да приступи.

Потребно је разликовати три главна термина:

Пример. Развити прилагођени елемент <uvit-student> који се ослања на Shadow DOM, као и веб страну која ће садржавати овакве елементе.

Прилагођени елеменат је дефинисан у посебној датотеци student-component.js, и садржај елемента се подешава у конструктору класе, тако што се искористи метод attachShadow, а потом по придруживању Shadow DOM-а постави својство innerHTML на жељену вредност:

class Student extends HTMLElement {
    constructor() {
        // увек у конструктору на почетку позвати super()
        super();
        // придруживање ДОМ сенке уз корен 
        const senkaKoren = this.attachShadow({ mode: 'open' });
        senkaKoren.innerHTML =
            `<h2>
                Ovo je student (ShadowDOM)!
            </h2>`;
    }

    connectedCallback() {}

    disconnectedCallback() {}

    attributeChangedCallback(attrName, oldVal, newVal) {}
}

// new element
let StudentComponent = customElements.define('uvit-student', Student);

Као и у претходном примеру, последњом наредбом доделе је реализовано повезивање класе прилагођеног елемента са етикетом <uvit-student>.

Веб страна која садржи овако направљене прилагођене елементе има следећи облик:

<!DOCTYPE html>
<html lang="sr">

<head>
    <title>Nove HTML komponente</title>
    <meta charset="UTF-8" />
    <script src='student-component.js'></script>
    <style type="text/css">
        uvit-student {
            color:red;    
        }
    </style>
</head>

<body>
    <h1>Илустрација нових <code>HTML</code> компоненти</h1>
    <p>
        Статичан садржај веб стране...
    </p>
    <uvit-student></uvit-student>
    <uvit-student></uvit-student>

</body>

</html>

И код ове веб стране се укључивање елемента <uvit-student> постиже помоћу <script> етикете.

Шаблони и HTML компоненте

Укључивање шаблона на клијентској страни дуго је било праћено одређеним недостатком стандарда, па су се у те сврхе користили семантички неисправне етикете као што су <script> или <textarea>, како би се спречило извршавање кода унутар тих тагова. Сада са новим <template> елементом може да се чува садржај који неће бити исртавана али ће бити доступан за коришћење.

Шаблони за компоненте се могу налазити у оквиру веб стране која садржи те компоненте.

Пример. Веб страну са прилагођеним елементом <uvit-student>, тако да се прилагођени елеменат ослања на Shadow DOM и користи шаблон дефинисан у оквиру веб стране.

Прилагођени елеменат, дефинисан у датотеци student-component.js, има следећу структуру:

class Student extends HTMLElement {
    constructor() {
        // увек у конструктору на почетку позвати super()
        super();
        // постављање шаблона
        const sablon = document
            .getElementById('uvit-student-template')
            .content;
        // придруживање ДОМ сенке уз корен 
        const senkaKoren = this.attachShadow({ mode: 'open' })
            .appendChild(sablon.cloneNode(true));
    }

    connectedCallback() {}

    disconnectedCallback() {}

    attributeChangedCallback(attrName, oldVal, newVal) {}
}

// нови елеменат
let StudentComponent = customElements.define('uvit-student', Student);

Уочава се да је пре подешавања Shadow DOM добијен шаблон тако што је у оквиру веб стране потражен елеменат са идентификатором uvit-student-template. Последњом наредбом доделе је реализовано повезивање класе прилагођеног елемента са етикетом <uvit-student>.

Веб страна која садржи шаблон за приказ прилагођених елемената, као и два прилагођена елемента има следећи облик:

<!DOCTYPE html>
<html lang="sr">
<head>
    <title>Nove HTML komponente</title>
    <meta charset="UTF-8" />
    <script src='student-component.js'></script>
</head>
<body>
    <h1>Илустрација нових <code>HTML</code> компоненти</h1>
    <p>
        Статичан садржај веб стране...
    </p>
    <template id="uvit-student-template">
        <style>
            h2 {
                color: white;
                background-color: #999;
                padding: 5px;
            }
        </style>
        <h2>
            Ovo je student (template)!
        </h2>
    </template>

    <uvit-student></uvit-student>
    <uvit-student></uvit-student>

</body>
</html>

Друго, елегантније и чешће решење је да се шаблон за компоненте налази у самосталној датотеци. Тада се обично и стил којим ће бити приказана дата компонента такође налази у самосталној датотеци.

Пример. Креирати веб страну са прилагођеним елементом <uvit-student>, тако да дефиниција прилагођеног елемента састоји од три самосталне датотеке: једне која описује садржај тог елемента (шаблон), друге која дефинише начин приказа (стил) и треће која описује понашање.

Начин приказа прилагођеног елемента (тј. стил) дефинисан у датотеци uvit-student-component.css:

h2 {
    color: white;
    background-color: #999;
    padding: 5px;
}

Структура прилагођеног елемента (тј. шаблон) дефинисанa je у датотеци uvit-student-component.html:

<!DOCTYPE html>
<link rel="stylesheet" href="uvit-student-component.css">
<h2>
    Ovo je student (separated template)!
</h2>

Уочава се да шаблон, коришћењем елемента <link rel="stylesheet" ...>, садржи референцу на стил.

Понашање прилагођене компоненте <uvit-student> је описано датотеком uvit-student-component.js:

fetch("uvit-student-component.html")
    .then(stream => stream.text())
    .then(text => define(text));

function define(html) {
    class Student extends HTMLElement {
        constructor() {
            // увек у конструктору на почетку позвати super()
            super();
            // придруживање ДОМ сенке уз корен 
            var shadow = this.attachShadow({mode: 'open'});
            shadow.innerHTML = html;
        }

        connectedCallback() { }

        disconnectedCallback() { }

        attributeChangedCallback(attrName, oldVal, newVal) { }
    }

    customElements.define('uvit-student', Student);
}

Овде је прво дохваћен садржај шаблона (самим тим и стила), а потом је дохваћени шаблон искоришћен (позивом функције define) за дефинисање стуктуре прилагођене компоненте и регистрацију одговарајуће етикете.

Веб страна сада не садржи шаблон за приказ прилагођених елемената, и она има следећи облик:

<!DOCTYPE html>
<html lang="sr">
<head>
    <title>Nove HTML komponente</title>
    <meta charset="UTF-8" />
    <script src="uvit-student-component.js"></script>
</head>
<body>
    <h1>Илустрација нових <code>HTML</code> компоненти</h1>
    <p>
        Статичан садржај веб стране...
    </p>

    <uvit-student></uvit-student>
    <uvit-student></uvit-student>
</body>
</html>

Приликом приказа у прегледачу, ова веб страна ће имати следећи изглед:

Прилагођени HTML елементи - одвојен шаблон

HTML компоненте, догађаји и атрибути

Често је јавља потреба да се хватају DOM догађаји над поједим деловима HTML компоненте која се развија и да се адекватно реагује на њих.

Пример. Креирати прилагођени елеменат који представља бројач, са могућношћу инкрементирања и декремнтирања, као и веб страну која садржи два таква бројача.

Начин приказа прилагођеног елемента (тј. стил) дефинисан у датотеци uvit-brojac-component.css:

button, p {
    display: inline-block;
    color: darkcyan;
}

Структура прилагођеног елемента (тј. шаблон) дефинисанa je у датотеци uvit-brojac-component.html:

<link rel="stylesheet" href="uvit-brojac-component.css">
<button aria-label="decrement">-</button>
    <p>0</p>
<button aria-label="increment">+</button>

Динамички аспекти прилагођене компоненте <uvit-brojac> дефинисани су у датотеци uvit-brojac-component.js:

fetch("uvit-brojac-component.html")
    .then(stream => stream.text())
    .then(text => define(text));

function define(html) {
    class Brojac extends HTMLElement {
        set value(value) {
            this._value = value;
            this.valueElement.innerText = this._value;
        }

        get value() {
            return this._value;
        }

        constructor() {
            super();
            this._value = 0;

            var shadow = this.attachShadow({mode: 'open'});
            shadow.innerHTML = html;

            this.valueElement = shadow.querySelector('p');
            var incrementButton = shadow.querySelectorAll('button')[1];
            var decrementButton = shadow.querySelectorAll('button')[0];

            incrementButton.onclick = () => this.value++;
            decrementButton.onclick = () => this.value--;
        }
    }

    customElements.define('uvit-brojac', Brojac);
}

Као и у претходном примеру, прво је дохваћен садржај шаблона (самим тим и стила), а потом је дохваћени шаблон искоришћен (у функцији define) за дефинисање стуктуре прилагођене компоненте и регистрацију одговарајуће етикете.

Овде вредност за _value садржи вредност бројача, и та вредност се исцртава приликом постављања бројача. У оквиру ове компоненте су хватани DOM догађаји onclick над дугмадима за инкрементацију и декрементацију која се налазе у оквиру ове компоненте.

Веб страна садржи два независна бројача, реализована као HTML компоненте:

<!DOCTYPE html>
<html>
<head>
    <title>Brojac</title>
    <script src="uvit-brojac-component.js"></script>
</head>
<body>
    Prvi brojac:
    <uvit-brojac></uvit-brojac>
    <br/>
    <br/>
    <br/>
    <br/>
    Drugi brojac:
    <uvit-brojac></uvit-brojac>
</body>
</html>

Приликом приказа у прегледачу, ова веб страна ће имати следећи изглед:

Прилагођени HTML елементи - бројачи

Међутим, нису ретке ситуације када се при раду са HTML компонентама креирају и процесирају нови догађаји. При раду са таквим догађајима, обично се консултују атрибути HTML компоненте.

Пример. Креирати прилагођени елеменат који представља фиоку, са могућношћу отврања и затварања и могућношћу да фиока буде онемогућена. Као одговор на догађај, фиока мења текст и боју. Креирати веб страну која садржи три такве фиоке, од којих је једна онемогућена.

Стил прилагођеног елемента је дефинисан у датотеци uvit-fioka-component.css:

h2 {
    color: white;
    background-color: #999;
    padding: 5px;
}

Шаблон за прилагођени елеменат је дефинисан у датотеци uvit-fioka-component.html:

<!DOCTYPE html>
<link rel="stylesheet" href="uvit-fioka-component.css">
<h2>
    Fioka (separated template)!
</h2>

Динамички аспекти прилагођене компоненте <uvit-fioka> дефинисани су у датотеци uvit-fioka-component.js:

fetch("uvit-fioka-component.html")
    .then(stream => stream.text())
    .then(text => define(text));

function define(html) {

    class Fioka extends HTMLElement {
        constructor() {
            // увек у конструктору на почетку позвати super()
            super();
            // придруживање ДОМ сенке уз корен 
            let senkaKoren = this.attachShadow({ mode: 'open' });
            senkaKoren.innerHTML = html;
            // реферисње на визуелни елеменат
            this.statusElement = senkaKoren.querySelector('h2');
            // постављање ослушкивача догађаја за click
            this.addEventListener('click', e => {
                // ако је ослушкивач онемогућен, клик се игнорише
                if (this.disabled)
                    return;
                this.pomeriFioku();
            });

        }

        connectedCallback() { }

        disconnectedCallback() { }

        // особина отворено
        get otvoreno() {
            return this.hasAttribute('otvoreno');
        }

        set otvoreno(val) {
            // Reflect the value of the open property as an HTML attribute.
            if (val) {
                this.setAttribute('otvoreno', 'XXX');
            } else {
                this.removeAttribute('otvoreno');
            }
        }

        // особина disabled
        get disabled() {
            return this.hasAttribute('disabled');
        }

        set disabled(val) {
            // Reflect the value of the disabled property as an HTML attribute.
            if (val) {
                this.setAttribute('disabled', '');
            } else {
                this.removeAttribute('disabled');
            }
        }

        static get observedAttributes() {
            return ['disabled', 'otvoreno'];
        }

        attributeChangedCallback(name, oldValue, newValue) {
            if (this.disabled) {
                this.setAttribute('tabindex', '-1');
                this.setAttribute('aria-disabled', 'true');
            } else {
                this.setAttribute('tabindex', '0');
                this.setAttribute('aria-disabled', 'false');
                if (this.otvoreno) {
                    this.statusElement.innerHTML = "otvoreno";
                    this.statusElement.setAttribute('style', 'background-color:green');
                } else {
                    this.statusElement.innerHTML = "zatvoreno";
                    this.statusElement.setAttribute('style', 'background-color:red');
                }
            }
        }

        pomeriFioku() {
            this.otvoreno = !this.otvoreno;
            console.log(`Fioka je pomerena. Status fioke: ${this.hasAttribute('otvoreno') ? 'otvoreno' : 'zatvoreno'}`);
        }

    }

    // нови елеменат
    customElements.define('uvit-fioka', Fioka);
}

Као и у претходном примеру, прво је дохваћен садржај шаблона (самим тим и стила), а потом је дохваћени шаблон искоришћен (у функцији define) за дефинисање стуктуре прилагођене компоненте и регистрацију одговарајуће етикете.

Овде су од интереса атрибути otvoreno и disabled које описују статус фиоке. У оквиру ове компоненте је направљен ослушкивач за догађај click за целу компоненту, где руковалац догађаја само промени атрибуте компоненте, а потом се хвата промена посматраних вредности атрибута и као реакција не ту промену се мења изглед елемената у оквиру прилагођене компоненте.

Веб страна садржи три фиоке, реализоване као HTML компоненте:

<!DOCTYPE html>
<html lang="sr">
<head>
    <title>Nove HTML komponente</title>
    <meta charset="UTF-8" />
    <script src="uvit-fioka-component.js"></script>
</head>
<body>
    <h1>Илустрација нових <code>HTML</code> компоненти</h1>
    <p>
        Статичан садржај веб стране...
    </p>

    <uvit-fioka otvoreno></uvit-fioka>
    <uvit-fioka></uvit-fioka>
    <uvit-fioka otvoreno disabled></uvit-fioka>
</body>
</html>

За прву фиоку је постављен атрибут otvoreno, за другу није, а за трећу је постављен и атрибут otvoreno и атрибут disabled.

Приликом приказа у прегледачу, претходна веб страна ће имати следећи изглед:

Прилагођени HTML елементи - фиоке

Литература

  1. Haverbeke M.: Eloquent JavaScript

  2. JavaScript - Mozzila Developer Network (MDN)

  3. Живановић, Д.: Веб програмирање - ЈаваСкрипт

  4. Copes F.: Complete JavaScript Handbook