O prelegere din cadrul materiei "aplicaţiilor Web la nivel de client" (CLIW), fondată și predată de Sabin Buraga la Facultatea de Informatică a Universității "Alexandru Ioan Cuza" din Iași.
Detalii la https://profs.info.uaic.ro/~busaco/teach/courses/cliw/web-film.html
9. Dr.SabinBuragawww.purl.org/net/busaco
Cod JavaScript intern vs.
preluat dintr-un fișier extern
<body>
…
<script type="text/javascript">
alert ("Salut, lume!");
</script>
</body>
<script type="text/javascript" src="http://salutari.info/salut.js">
</script>
remarcă: pentru browser-ele moderne poate fi specificat
application/javascript în loc de text/javascript
10. Dr.SabinBuragawww.purl.org/net/busaco
arhitectura de bază a interpretorului (script engine) JS
inclus într-un navigator Web (A. Zlatkov, 2017)
blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
12. Dr.SabinBuragawww.purl.org/net/busaco
Un program JavaScript are acces la arborele DOM
(Document Object Model)
corespunzător documentului HTML
specificații ale Consorțiului Web
varianta în vigoare: DOM 4 (2015)
www.w3.org/TR/dom/
de revăzut materia „Tehnologii Web”
profs.info.uaic.ro/~busaco/teach/courses/web/
13. Dr.SabinBuragawww.purl.org/net/busaco
Un program JavaScript are acces la arborele DOM
(Document Object Model)
corespunzător documentului HTML
DOM Living Standard – dom.spec.whatwg.org
specific HTML5, în continuă dezvoltare
(cea mai recentă actualizare: 21 noiembrie 2017)
18. Dr.SabinBuragawww.purl.org/net/busaco
dom: core
un document HTML și arborele DOM corespunzător
reprezentat via Live DOM Viewer
http://software.hixie.ch/utilities/js/live-dom-viewer/
în cazul HTML, numele elementelor
sunt disponibile cu litere mari (capitals)
20. Dr.SabinBuragawww.purl.org/net/busaco
De asemenea, programele JavaScript au acces
la diverse obiecte oferite de mediul de execuție
pus la dispoziție de browser
e.g., informații privind contextul rulării
(caracteristici ale navigatorului, latența rețelei),
istoricul navigării, fereastra de redare a conținutului,
transfer (a)sincron de date,…
22. Dr.SabinBuragawww.purl.org/net/busaco
interface HTMLDocument : Document {
attribute DOMString title; // titlul documentului
readonly attribute DOMString referrer; // adresa resursei ce referă pagina
readonly attribute DOMString domain; // domeniul de care aparține
readonly attribute DOMString URL; // URL-ul absolut al documentului
attribute HTMLElement body; // acces la elementul <body>
readonly attribute HTMLCollection images; // lista tuturor imaginilor
readonly attribute HTMLCollection links; // lista tuturor legăturilor
readonly attribute HTMLCollection forms; // lista tuturor formularelor
attribute DOMString cookie; // acces la cookie-uri
// emite o excepție dacă e asignată o valoare
void open (); // deschide un flux de scriere (alterează DOM-ul curent)
void close (); // închide fluxul de scriere și forțează redarea conținutului
void write (in DOMString text); // scrie un șir de caract. (e.g., cod HTML)
void writeln (in DOMString text); // idem, dar inserează și new line
NodeList getElementsByName (in DOMString numeElement);
// furnizează o listă de elemente conform unui nume de tag
}; interfață specificată în limbajul declarativ WebIDL
33. Dr.SabinBuragawww.purl.org/net/busaco
function topLevelNodeAt (nod, top) {
while (nod && nod.parentNode != top)
nod = nod.parentNode;
return nod;
}
function topLevelNodeBefore (nod, top) {
while (!nod.previousSibling && nod.parentNode != top)
nod = nod.parentNode;
return topLevelNodeAt (nod.previousSibling, top);
}
discuție
Ce rol au cele două funcții?
35. Dr.SabinBuragawww.purl.org/net/busaco
// un element HTML generic
interface HTMLElement : Element {
attribute DOMString id; // identificator asociat elementului
attribute DOMString title; // titlu explicativ
attribute DOMString lang; // limba în care e redactat conținutul
attribute DOMString className; // numele clasei CSS folosite pentru redare
};
// specifică un formular Web
interface HTMLFormElement : HTMLElement {
readonly attribute HTMLCollection elements; // elementele HTML incluse în formular
readonly attribute long length; // numărul câmpurilor formularului
attribute DOMString action; // URI-ul resursei ce procesează datele
attribute DOMString enctype; // tipul MIME de codificare a datelor
// (e.g., application/x-www-form-urlencoded)
attribute DOMString method; // metoda HTTP folosită: GET, POST
void submit(); // trimite date URI-ului definit de ‘action’
};
// o imagine (conținut grafic raster)
interface HTMLImageElement : HTMLElement {
attribute DOMString alt; // text alternativ descriind conținutul grafic
attribute DOMString src; // URI-ul resursei grafice
};
36. Dr.SabinBuragawww.purl.org/net/busaco
Interfața HTMLCollection modelează o listă de noduri
un nod poate fi accesat folosind un index numeric
sau pe baza unui identificator (e.g., stabilit via atributul id)
interface HTMLCollection {
readonly attribute unsigned long length; // oferă lungimea listei
Node item (in unsigned long index); // oferă un nod via un index numeric
Node namedItem (in DOMString name); // furnizează un nod pe baza numelui
};
46. Dr.SabinBuragawww.purl.org/net/busaco
// funcție care generează un număr de elemente HTML
// pe care le adaugă elementului identificat prin 'identificator'
function genereazaElemente (numarElem, numeElem, identificator) {
for (var it = 0; it < numarElem; it++) {
// creăm un element specific
var element = document.createElement (numeElem);
// ...și-i atașăm un nod text
var text = document.createTextNode ("Salut, lumea...");
element.appendChild (text);
// adăugăm nodul creat
document.getElementById (identificator).appendChild (element);
}
}
genereazaElemente (3, "div", "continut"); // 3 <div>-uri
genereazaElemente (2, "p", "lumi"); // 2 paragrafe (<p>)
<div id="lumi"></div>
<h1 id="continut"></h1> a se studia exemplele
din arhivă
53. Dr.SabinBuragawww.purl.org/net/busaco
Suport pentru procesarea proprietăților CSS
actualmente, modificarea proprietăților de stil
se poate realiza via proprietatea HTMLElement.style
// asocierea mai multor stiluri CSS
elem.style.cssText = "color: blue; border: 1px solid #000";
// similar cu:
elem.setAttribute ("style", "color: blue; border: 1px solid #000;");
developer.mozilla.org/Web/API/CSSStyleDeclaration
54. Dr.SabinBuragawww.purl.org/net/busaco
Găsirea de noduri via selectori CSS
Selectors API – Level 1
recomandare W3C (2013)
acces la diverse date via selectorii CSS cu metodele
query() queryAll() querySelector() querySelectorAll()
www.w3.org/TR/selectors-api/
55. Dr.SabinBuragawww.purl.org/net/busaco
Găsirea de noduri via selectori CSS
querySelector (selectori)
furnizează primul element – folosind traversarea
în adâncime în preordine – care se potrivește grupului
de selectori (delimitați de virgulă)
querySelectorAll (selectori)
oferă lista de tip NodeList a tuturor elementelor găsite
56. Dr.SabinBuragawww.purl.org/net/busaco
Găsirea de noduri via selectori CSS
// toate elementele <li> selectate via CSS (date de tip NodeList)
var elemente = document.querySelectorAll ("ul.menu > li");
for (var i = 0; i < elemente.length; i++) {
prelucrează (elemente.item (i)); // procesăm fiecare nod
}
95. Dr.SabinBuragawww.purl.org/net/busaco
for (var i = 0; i < 6; i++) { // construim tabelul cu câmpuri de intrare
var row = document.querySelector ("table").insertRow (-1); // adăugăm un rând la tabel
for (var j = 0; j < 6; j++) {
var letter = String.fromCharCode ("A".charCodeAt (0) + j - 1);
row.insertCell (-1).innerHTML = i && j ? "<input id='" + letter + i + "'/>" : i || letter;
}
}
var DATA={}, INPUTS=[].slice.call (document.querySelectorAll ("input"));
INPUTS.forEach ( function(elm) { // pentru fiecare element <input>, procesăm valoarea
elm.onfocus = function (e) { e.target.value = localStorage[e.target.id] || ""; };
elm.onblur = function (e) { localStorage[e.target.id] = e.target.value; computeAll(); };
var getter = function () { // funcție de furnizare a valorii (deja stocată în localStorage)
var value = localStorage[elm.id] || "";
if (value.charAt(0) == "=") { // începe cu =, deci e o formulă care va fi evaluată
with (DATA) return eval (value.substring(1));
} else { return isNaN (parseFloat (value)) ? value : parseFloat (value); }
};
Object.defineProperty (DATA, elm.id, { get: getter });
Object.defineProperty (DATA, elm.id.toLowerCase(), { get: getter });
} );
( window.computeAll = function () {
INPUTS.forEach (function (elm) { try { elm.value = DATA[elm.id]; } catch(e) {} });
} )();
101. Dr.SabinBuragawww.purl.org/net/busaco
Browser-ul Web oferă obiectul Window
proprietăți importante:
document – referință la documentul (arborele DOM)
pe care obiectul Window îl include
history – acces la istoricul navigării (via obiectul History)
https://html.spec.whatwg.org/multipage/history.html
location – adresa Web (URL) curentă
navigator – oferă date despre browser (un obiect Navigator)
https://developer.mozilla.org/Web/API/Navigator
102. Dr.SabinBuragawww.purl.org/net/busaco
Browser-ul Web oferă obiectul Window
proprietăți importante – continuare:
screen – oferă detalii despre ecran (obiectul Screen)
https://developer.mozilla.org/Web/API/Screen
localStorage – date persistente stocate local
(obiectul LocalStorage specificat de HTML5)
https://developer.mozilla.org/Web/API/Window/localStorage
sessionStorage – referință la date vizând sesiunea curentă
(stocate într-un obiect SessionStorage oferit de HTML5)
html.spec.whatwg.org/multipage/webstorage.html
103. Dr.SabinBuragawww.purl.org/net/busaco
Browser-ul Web oferă obiectul Window
metode utile:
alert() confirm() prompt() postMessage()
focus() blur() find() print()
open() close() stop()
moveBy() moveTo() resizeBy() resizeTo()
scrool() scrollBy() scrollTo()
amănunte: https://drafts.csswg.org/cssom-view/
104. Dr.SabinBuragawww.purl.org/net/busaco
Browser-ul Web oferă obiectul Window
evenimente – specificate prin nume prefixat de on
și clasificate ca fiind WindowEventHandlers
(reuniunea evenimentelor definite de interfețele
Window, HTMLBodyElement și HTMLFrameSetElement)
developer.mozilla.org/Web/API/WindowEventHandlers
110. Dr.SabinBuragawww.purl.org/net/busaco
AJAX – Asynchronous JavaScript And XML
suită de tehnologii deschise
interschimb și manipulare de date reprezentate prin:
diverse dialecte XML,
JSON (JavaScript Object Notation),
HTML,
alte formate
118. Dr.SabinBuragawww.purl.org/net/busaco
Obiectul XMLHttpRequest
send ( )
transmite (asincron) date – e.g., JSON, XML etc. –,
spre aplicația/serviciul ce rulează pe server
orice listener (asociat evenimentelor onloadstart,
onprogress, onload, onloadend, ontimeout, onabort, onerror)
trebuie stabilit înainte de a trimite date
135. Dr.SabinBuragawww.purl.org/net/busaco
const URL = 'https://www.random.org/sequences/?min=1&max=33&col=1&format=plain';
const TIME = 2000;
var xhr = new XMLHttpRequest ();
var numbers = document.getElementById ('numbers');
// eveniment de tratare a expirării timpului de așteptare
xhr.ontimeout = function () { numbers.textContent = 'Time-out... :('; };
// eveniment de tratare a preluării datelor solicitate unui serviciu
xhr.onload = function () {
if (xhr.readyState === 4) { // am primit datele
if (xhr.status === 200) { // răspuns Ok din partea serverului
// înlocuim spațiile albe cu virgulă și plasăm conținutul
// în cadrul elementului HTML identificat prin 'numbers'
numbers.textContent = xhr.responseText.trim ().replace (/W+/g, ', ');
} else {
numbers.textContent = 'An error occurred: ' + xhr.statusText;
}
}
};
xhr.open ("GET", URL, true); // deschidem conexiunea
xhr.timeout = TIME; // stabilim timpul maxim de așteptare a răspunsului
xhr.send (null); // nu expediem date
137. Dr.SabinBuragawww.purl.org/net/busaco
function status(response) {
// recurgem la promises pentru a realiza procesări în funcție de codul de stare HTTP
if (response.status >= 200 && response.status < 300) {
return Promise.resolve (response) // cererea poate fi rezolvată
} else {
return Promise.reject (new Error (response.statusText)) // cererea a fost rejectată
}
}
fetch (URL)
.then (status) // verificăm dacă datele au fost recepționate cu succes
.then ((response) => response.text ()) // transformăm obiectul răspunsului în șir de caract.
.then (function (response) { // procesăm secvența de numere
// înlocuim spațiile albe cu virgulă și plasăm conținutul
// în cadrul elementului HTML identificat prin 'numbers'
var numbers = document.getElementById ('numbers');
numbers.textContent = response.trim ().replace (/W+/g, ', ');
})
.catch (function (error) { // a survenit o eroare :(
numbers.textContent = 'An error occurred: ' + error;
});
revezi cursul anterior
138. Dr.SabinBuragawww.purl.org/net/busaco
Scenariu:
preluăm caractere de la client – introduse în <textarea> –
și le trimitem asincron prin POST unei aplicații Web
rulând pe server care le expediază înapoi
mesaje vehiculate în format JSON
{ "tasta": "caracter", "data": "secunde" }
tratăm evenimentul keypress pentru a capta tastele
acționate de utilizator
studiu de caz: PostJSON
139. Dr.SabinBuragawww.purl.org/net/busaco
const trateazaEveniment = ev => { // tratăm evenimentul de apăsare a unei taste
// mesajul propriu-zis trimis serverului prin POST atunci când survine evenimentul
let msg = `{ "tasta": "${String.fromCharCode (ev.charCode)}", "data": "${Date.now()}" }`;
let request = new Request ('/ajax/post.php', { // încapsulăm o cerere POST
method: 'POST',
body: JSON.stringify (msg), // convertim datele JSON în șir de caractere
headers: {} // n-avem câmpuri-antet
});
fetch (request) // promitem să executăm codul...
.then (response => { // verificăm dacă am primit date JSON de la server
var contentType = response.headers.get ('Content-Type');
if (contentType && contentType.includes('application/json')) {
return response.json (); };
throw new TypeError ('Datele primite nu-s JSON :('); })
.then (json => { // procesăm efectiv datele
// creăm un nod text care indică tasta apăsată
let elem = document.createTextNode (json.tasta);
document.getElementById ('tasteApasate').appendChild (elem);
// raportăm datele primite și la consola browser-ului
console.log (`Date JSON primite: tasta=${json.tasta}, data=${Date(json.data)}`);
})
};
document.addEventListener ('keypress', trateazaEveniment);
140. Dr.SabinBuragawww.purl.org/net/busaco
<?php
// post.php -- program PHP care preia date JSON
// transmise via POST de client și le trimite înapoi (echo)
function eJSONValid ($sir) { // verifică dacă datele JSON sunt corecte
json_decode ($sir);
return json_last_error () == JSON_ERROR_NONE;
}
// preluăm de la intrarea standard datele transmise de client (raw data)
// (aici, cele dintr-o cerere POST)
$date = trim (file_get_contents ("php://input"));
if (eJSONValid ($date)) { // trimitem datele JSON înapoi dacă sunt în regulă
header ("Content-type: application/json");
echo json_decode ($date);
} else {
die ('Date incorecte');
}
?>
aplicația (serviciul) invocat(ă) pe server
143. Dr.SabinBuragawww.purl.org/net/busaco
Oferirea de alternative la Ajax, atunci când suportul
pentru acesta nu este implementat/activat
graceful degradation
progressive enhancement
www.w3.org/wiki/Graceful_degradation_versus_progressive_enhancement
150. Dr.SabinBuragawww.purl.org/net/busaco
Șabloane de proiectare AJAX
privind programarea:
invocare de servicii Web (RESTful Service, JSON Message)
dialog între navigatorul Web și server
(Periodic Refresh, Submission Throttling, Cross-Domain Proxy)
asigurarea performanței (Fat Client, Browser-Side Cache,
Guesstimate, Predictive Fetch, Code Compression, On-Demand JS)
popularea arborelui DOM
151. Dr.SabinBuragawww.purl.org/net/busaco
Șabloane de proiectare AJAX
referitoare la interacțiunea cu utilizatorul:
formulare Web
(Live Form, Data Grid, Progress Indicator, Suggestion, Slider)
widget-uri de afișare a conținutului
arhitectura informației
funcționalitate (Lazy Registration, Direct Login, Heartbeat,
Autosave, Unique URLs)
158. Dr.SabinBuragawww.purl.org/net/busaco
Ajax/Comet oferă suport pentru dezvoltarea de
aplicații Web hibride – mash-ups
combinarea – în contextul nostru, la nivel de client –
a conținutului ce provine din surse (situri)
multiple, oferind o funcționalitate/experiență nouă
162. Dr.SabinBuragawww.purl.org/net/busaco
Same-Origin Security Policy
“restricts how a document or script loaded from one origin
can interact with a resource from another origin”
astfel, un program JavaScript trebuie să acceseze
doar datele aparținând aceleași origini
– i.e., provenite din același domeniu Internet
163. Dr.SabinBuragawww.purl.org/net/busaco
se permit doar transferuri vizând reprezentări de resurse
referitoare la imagini, fișiere CSS
și alte programe JavaScript aparținând aceleași origini
client
server
Web
HTTP
JSON, XML,…
API
public
API
public
HTTP
JPG
HTTP
JPG
Same-Origin Policy
165. Dr.SabinBuragawww.purl.org/net/busaco
interactiune web: ajax – studiu de cazvar url = "http://profs.info.uaic.ro/~busaco/teach/courses/web/web-film";
// realizăm o cerere HEAD pentru a obține meta-date despre o resursă
var client = new XMLHttpRequest ();
client.open ("HEAD", url, true);
client.send ();
client.onreadystatechange = function () {
// am recepționat câmpurile-antet?
if (client.readyState == 2) {
// semnalăm tipul MIME și data ultimei actualizări
alert ("Resursa de tip '" +
client.getResponseHeader ("Content-Type") + "' s-a actualizat la " +
client.getResponseHeader ("Last-Modified"));
}
}
preluarea cu HEAD a unor
meta-date, în mod asincron
167. Dr.SabinBuragawww.purl.org/net/busaco
CORS (Cross-Origin Resource Sharing)
recomandare a Consorțiului Web (2014)
www.w3.org/TR/cors/
permite partajarea la nivel de client a resurselor
provenind din domenii Internet diferite
astfel, se pot emite cereri între domenii (cross-origin)