0% au considerat acest document util (0 voturi)
72 vizualizări189 pagini

Comunicare Asincrona, Browser Sniffing, XHR, Firebug

Documentul prezintă mecanismul de bază al comunicării asincrone AJAX prin obiectul XHR. Este descris procesul de trimitere a unei cereri către server și prelucrarea răspunsului primit pentru a actualiza elementele din pagină.

Încărcat de

trc1
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca DOCX, PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
72 vizualizări189 pagini

Comunicare Asincrona, Browser Sniffing, XHR, Firebug

Documentul prezintă mecanismul de bază al comunicării asincrone AJAX prin obiectul XHR. Este descris procesul de trimitere a unei cereri către server și prelucrarea răspunsului primit pentru a actualiza elementele din pagină.

Încărcat de

trc1
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca DOCX, PDF, TXT sau citiți online pe Scribd
Sunteți pe pagina 1/ 189

Comunicare asincrona, browser sniffing, XHR, Firebug

Reminder teoretic:

AJAX=

HTML: pt structurarea paginii, de obicei prin DIV si SPAN, dar nu obligatoriu (important: recapitulaţi de
la grafică diferenţele între DIV şi SPAN!)

+ CSS: pt formatarea elementelor din pagină, inclusiv poziţionare, ascundere etc. (recapitulaţi CSS de la
grafică!)

+ JavaScript: pt. modificarea structurii HTML şi a proprietăţilor CSS, în mod dinamic, prin functii asociate
evenimentelor ce pot avea loc asupra elementelor paginii; de obicei se realizează prin manipularea
arborelui DOM dar se pot folosi si frameworkuri precum JQuery, Prototype etc.

+ comunicare asincronă cu serverul = schimb de date ce nu întrerupte activitatea curentă a acelor părţi
din pagină care nu au nevoie imediată de datele cerute de la server; se implementează prin 2 metode:
obiectul XHR sau cadrele IFRAME invizibile (pentru situaţiile care nu pot fi implementate prin XHR, cum
ar fi uploadul de fişiere, gestiunea butoanelor Back-Forward din browser etc.)

+XML: acesta e formatul standard în care serverul ar trebui să îşi împacheteze răspunsul; însă nu e
obligatoriu, în practică se preferă ca serverul să răspund cu stringuri convenabile - de ex. valori separate
prin virgulă - sau stringuri JSON – o alternativă mult mai performantă la XML – sau chiar stringuri ce pot
fi convertite în cod JavaScript – aşa numita tehnică remote scripting.

AJAX e complet independent de server şi de limbajul folosit pe server, singurul lucru care contează este
ca serverul să fie capabil să răspundă cu stringuri la cererile ce vin de la browser (orice server face asta).
Deoarece sunteţi familiarizaţi cu PHP vom folosi scripturi PHP simple pentru a genera răspunsul.
Consideraţi ca temă să modificaţi exemplele astfel încât în PHP răspunsul să fie construit cu date dintr-o
bază de date MySQL.
Următorul exemplu prezintă mecanismul de bază al comunicării asincrone.

Se creează un formular ce solicită adresa poştală a utilizatorului. Imediat după ce codul poştal e tastat, la
momentul trecerii în următoarea casetă, codul poştal e trimis la server, care răspunde cu strada şi
celelalte detalii pe care le are stocate despre acel cod poştal.

Pagina Client (fisier de tip HTML):

<html>

<head>

<title>Formular</title>

<script type="text/javascript">
Crearea obiectului XHR

Fixarea unei funcţii (fără paranteze!) de


function schimbDate(codpostal) prelucrare a răspunsului, pentru cand
{ acesta va sosi

xhr=creareXHR() Trimiterea argumentului la scriptul


codpostal.php, cu codul concatenat ca
xhr.onreadystatechange=procesare
variabilă GET
xhr.open("GET","codpostal.php?CP="+codpostal)

xhr.send(null)

}
Browser sniffing:

function creareXHR() - încearcă crearea obiectului


XHR în Internet Explorer mai
{ vechi şi mai nou

try {xhr=new ActiveXObject("Msxml2.XMLHTTP")}

catch (e) - dacă încercările au eşuat,


testează existenţa clasei
{ XMLHttpRequest şi în caz
afirmativ creează obiectul
try {xhr=new ActiveXObject("Microsoft.XMLHTTP")}

catch (e)

{xhr=false}

if (!xhr&&typeof(XMLHttpRequest)!='undefined')

xhr=new XMLHttpRequest()

return xhr

Testarea răspunsului:
- dacă răspunsul a sosit integral (readyState 4) si dacă e
răspuns normal (status 200)
Obs: nu legati cu && in acelasi IF! trebuie testate pe
rand, caci in unele browsere atributul status se creeaza
abia cand readyState devine 4, inainte de asta va da
eroare de variabila nedefinita (daca serverul intarzie)
function procesare()

if (xhr.readyState == 4)

if (xhr.status == 200)
Prelucrarea raspunsului:
{
- Raspunsul e asteptat ca text brut
raspuns=xhr.responseText cu 3 valori delimitate de virgula
(split sparge stringul intr-un
date=raspuns.split(',')
vector de 3 elemente, folosind
document.getElementById("TXoras").value=date[0] virgula ca separator)

document.getElementById("TXjudet").value=date [1] - Datele sunt atribuite campurilor


existente in formularul
document.getElementById("TXadresa").value=date [2]

else

codHTMLeroare='<span style="color:red">Eroare transfer! </span>'

document.getElementById("Eroare").innerHTML =codHTMLeroare

</script>

</head> In caz de eroare, se insereaza un


mesaj in pagina, acolo unde exista
ID=Eroare; de remarcat folosirea lui
innerHTML pentru inserare de cod
HTML în pagină!!!
<body>

<h1>Introduceti datele</h1>

<form action="destinatie.php" method="post">

<table>
<tr>
Campul ce va
declansa
<td>Nume</td>
schimbul de date
<td><input type="text" id="TXnume" ></td> la momentul
ONBLUR
</tr>

<tr>

<td>CodPostal</td>

<td><input type="text" id="TXcodpostal" onblur="schimbDate(this.value)"></td>

</tr>

<tr>

<td>Adresa</td>

<td><input type="text" id="TXadresa" size="50"></td>

</tr>

<tr>
Campurile ce vor fi
<td>Oras</td> completate cu
raspunsul serverului
<td><input type="text" id="TXoras" ></td>
(vezi IDurile cu care
sunt accesate din
</tr>
functia de procesare)
<tr>

<td>Judet</td>

<td><input type="text" id="TXjudet" ></td>

</tr>

<tr>

<td colspan="2"><input type="submit" value="Trimite formular" ></td>

</tr>

</table>
Paragraf gol, rezervat pentru mesajul de eroare
</form> (O variantă ar fi ca paragraful să conţină de la început
mesajul de eroare, dar să fie ascuns cu proprietatea CSS
"display:none")
<p id="Eroare"></p>

</body>

</html>

Scriptul server

(salvat in asa fel incat sa fie gasit de obiectul XHR de mai sus: cu numele codpostal.php in htdocs):

<?php

if ($_GET["CP"]=="400451")

print "Cluj Napoca,Cluj,Aleea Azuga";

else

print "cod incorect,cod incorect,cod incorect";

?>

 După cum indică scriptul server, acesta poate raspunde cu date corecte doar la codul 400451! In
rest, se returneaza "cod incorect" (intr-un scenariu realist, ar exista o baza de date cu toate
codurile si strazile/orasele asociate lor)

 Verificati si inserarea erorii in pagina (prin oprirea serverului ceea ce va afecta codurile de
raspuns 4/200)

 Urmariti consola Firebug pentru a monitoriza datele schimbate intre client si server
 Fiecare transfer XHR apare in rubrica Console prin indicativul GET urmat de URLul contactat. Daca dati click pe
un transfer GET apar rubricile Params (datele trimise), Response (raspunsul serverului), Headers (antetul HTTP
integral). Aceleasi informatii se pot obtine si de la rubrica Net, dar aceea e mai aglomerata, caci afiseaza TOATE
transferurile (inclusiv imaginile, foile de stil si alte fisiere ce vin de la server). Rubrica Net o folositi cand vreti sa
testati transferul prin cadre invizibile (care nu se reflecta in consola, caci nu se fac prin XHR!).

 Verificati si rubrica HTML, care va desena arborele DOM; pentru fiecare element selectat din DOM, in dreapta
ecranului apare rubrica DOM cu lista cu proprietatile obiectuale ale elementului (nodeName, nodeType,
childrenNodes, firstChild, firstElementChild, etc.)

Obs1: Firebug e disponibil doar in Firefox. Pentru Internet Explorer se pot folosi produse gratuite
similare, precum Fiddler.

Obs2: Legat de linia xhr.onreadystatechange=procesare:

De ce nu se pun paranteze la numele functiei? Daca s-ar pune, functia s-ar executa automat cand
scriptul executa acea linie si nu ar mai astepta pana la aparitia evenimentului!
Detalii: in JavaScript exista notiunea de "variabila de tip functie", adica o variabila a carei valoare e
corpul unei functii (si nu valoarea returnata!). Cu ajutorul variabilelor de tip functie se pot realiza
diverse trucuri de programare:

- schimbarea numelui unei functii (prin simple atribuiri de variabile)

- crearea de functii care primesc ca argument alte functii

- crearea de functii care returneaza functii

In principiu, cand vedeti o functie fara paranteze (ex: procesare in loc de procesare()) inseamna ca e
vorba de o variabila ce contine corpul functiei si nu de valoarea returnata)

EX:

function f() {…………}

a=f

Din acest moment, functia f se poate apela si cu a()

Mai mult, se poate atribui direct o functie unei variabile:

a=function {……………}

(functiei nu i se mai da nume dupa cuvantul function! vom spune ca functia de la dreapta egalului este
o functie ANONIMA; metoda se practica frecvent la functiile asociate evenimentele: evenimentului I se
atribuie direct corpul functiei, fara ca aceasta sa fi fost definita inainte – asta presupune ca nu dorim
ca acea functie sa o mai apelam si altfel decat prin acel eveniment (in caz contrar, trebuie sa-I dam
nume)

Obs3:

- spre deosebire de siteurile clasice, datele trimise prin GET nu se mai vad in bara de adresa;

- erorile din scriptul server nu se mai manifesta printr-un mesaj de eroare afisat in browser (de genul
syntax error on line x) – mesajul e stocat in obiectul XHR (putem sa-l inseram in pagina).

Sarcina de lucru: Sa se extinda scriptul pentru a cauta codul postal intr-o baza de date a codurilor
postale (cu 5 inregistrari).
Următorul este un exemplu de trimitere a unui formular integral prin metoda POST, folosind XHR:

<html>

<script type="text/javascript">

function creareXHR()

try

{ xhr = new ActiveXObject("Msxml2.XMLHTTP")}

catch (e)

try { xhr = new ActiveXObject("Microsoft.XMLHTTP") }

catch (e)

{ xhr = false }

if (!xhr && typeof XMLHttpRequest !="undefined")

xhr = new XMLHttpRequest()

Extragerea
function trimite() explicită a
datelor din
{ formular

creareXHR()
valori=new Object()

valori["nume"]=document.getElementById("TXnume").value

valori["codpostal"]=document.getElementById("TXcodpostal").value

valori["adresa"]=document.getElementById("TXadresa").value
Concatenarea
valori["oras"]=document.getElementById("TXoras").value datelor din
formular într-un
valori["judet"]=document.getElementById("TXjudet").value QueryString cu
conversie de
sir="" caractere
nepermise
for (cheie in valori)

sir=sir+cheie+"="+encodeURIComponent(valori[cheie])+"&"

} Setarea antetului
HTTP pentru
xhr.open("POST","raspuns.php") trimiterea de
formulare
xhr.setRequestHeader ("Content-Type", "application/x-www-form-urlencoded")

xhr.onreadystatechange=procesare

xhr.send(sir)

function procesare()

if (xhr.readyState!=4) return

alert(xhr.responseText)

</script>

</head> Formularul NU
are METHOD,
ACTION si buton
SUBMIT! Rolul
acestora e
preluat de XHR
<body>

<h1>Introduceti datele</h1>

<form>

<table>

<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal></td>

</tr>

<tr>

<td>Adresa</td>

<td><input type=text id=TXadresa size=50></td>

</tr>

<tr>

<td>Oras</td>

<td><input type=text id=TXoras ></td>

</tr>

<tr>

<td>Judet</td>

<td><input type=text id=TXjudet ></td>

</tr>
<tr>

<td></td>

<td><input type="button" value="Trimite" onclick="trimite()" ></td>

</tr>

</table>

</form>

<div id="rasp"></div>

</body>

</html>
Următorul exemplu arată cum trebuie procedat când aceeaşi pagină trebuie să realizeze mai multe
comunicări asincrone în timpul utilizării sale. În general e valabil acest lucru, deoarece paginile Web
complexe suportă diverse evenimente şi fiecare eveniment poate declanşa comunicări asincrone cu
diverse scripturi server - spre deosebire de siteurile clasice unde un formular poate primi răspunsuri
doar de la o pagină PHP (pe de altă parte, acea pagină poate implica mai multe pagini folosind require şi
include; aici însă acelaşi script JavaScript poate dialoga cu mai multe scripturi PHP prin ACELAŞI obiect
XHR).

Pagina de mai jos comunică cu 2 scripturi PHP şi pentru fiecare din acestea alocă o altă funcţie de
procesare a răspunsului (chiar dacă acestea sunt aproape identice – în cazuri realiste acestea diferă
semnificativ).

<html>
Funcţia de
<head> iniţializare
creează obiectul
<title>Formular</title> XHR şi pregăteşte
DIVul în care se
<script type="text/javascript"> va insera
răspunsul
serverului
function initializari()

tinta=document.getElementById('d')

try {xhr=new ActiveXObject("Msxml2.XMLHTTP")}

catch (e)

try {xhr=new ActiveXObject("Microsoft.XMLHTTP")} Funcţiile care


configurează
catch (e) obiectul XHR
pentru
{xhr=false} comunicare cu 2
scripturi diferite,
} şi 2 funcţii de
procesare diferite
if (!xhr&&typeof(XMLHttpRequest)!='undefined')

xhr=new XMLHttpRequest()
}

function dialogscript1()

xhr.onreadystatechange=procesare1

xhr.open("GET","script1.php")

xhr.send(null)

function dialogscript2()

xhr.onreadystatechange=procesare2

xhr.open("GET","script2.php")

xhr.send(null)

Funcţiile care
configurează
obiectul XHR
function procesare1() pentru
comunicare cu 2
{ scripturi diferite,
şi 2 funcţii de
if (xhr.readyState == 4)
procesare diferite
if (xhr.status == 200)

{
raspuns=xhr.responseText

tinta.innerHTML=raspuns

else

alert('Eroare conexiune 1')

function procesare2()

if (xhr.readyState == 4)

if (xhr.status == 200)

raspuns=xhr.responseText

tinta.innerHTML=raspuns

else

alert('Eroare conexiune 2')

</script>

</head>

<body onload="initializari()">

<input type="button" value="click pt dialog cu primul script" onclick="dialogscript1()" />

<input type="button" value="click pt dialog cu al doilea script" onclick="dialogscript2()"/>


<div id="d">Aici va apare raspunsul serverului</div>

</body>

</html>

Observaţie importantă: când ştim că pagina HTML va dialoga de mai multe ori, chiar cu mai multe
scripturi server, NU are rost să creăm obiectul XHR de FIECARE dată.

Ar trebui să ştiţi (de la grafică) că JavaScript are particularitatea că variabilele create de o funcţie rămân
accesibile şi celorlalte funcţii, nu se distrug.

Asta înseamnă că la onload putem apela o funcţie de iniţializare care să creeze obiectul XHR şi orice alte
variabile vor fi necesare în continuare, apoi evenimentele din pagină să apeleze funcţii ce folosesc
obiectele existente, fără să le mai creeze încă o dată!

In cazul de fata functia initializari creează obiectul XHR si obiectul tinta (unde se va insera raspunsul),
apoi functiile dialogscript1 si dialogscript2 (care schimba date cu scripturi diferite) reutilizeaza obiectele
existente.

Alta obs. importanta: scripturile PHP trebuie sa vina de pe ACELASI server ca si pagina HTML. Nici
obiectul XHR, nici cadrele invizibile nu pot primi date de la alte servere decat cel pe care se afla fisierul
ce le contine (de fapt cadrele invizibile pot, dar pagina principală nu are voie să acceseze conţinutul
lor!)

Alta obs: Exemplul aici prezentat presupune că cele două dialoguri nu au loc deodată. Dacă a doua
cerere XHR începe înainte să se fi terminat prima, răspunsul primeia se PIERDE! Asta înseamnă că
trebuie să folosim o variabilă flag care să indice dacă obiectul XHR s-a eliberat:

In cazul de faţă e vorba ca după citirea unui răspuns flagul să fie comutat pentru a indica eliberarea
obiectului XHR:

raspuns=xhr.responseText
xhrliber=true

Apoi înaintea fiecărui apel XHR se testează acest lucru şi dacă XHRul nu este liber, se creează unul nou:

if (xhrliber)

xhr.open(…)

xhr.send(…)

else

creeazaxhr()

Eventual, se creează ambele obiecte XHR în funcţia de iniţializare, împreună cu câte un flag pentru
fiecare, apoi la iniţierea fiecărei comunicări asincrone se testează mai întâi care XHR e liber.

De obicei se lucrează cu 2 obiecte XHR care preiau alternativ sarcinile de comunicare asincronă, cu
posibilitatea de a realiza schimburi paralele cu serverul. E recomandat să nu ne bazăm pe mai mult de 2
obiecte XHR transferând date în paralel, căci unele browsere nu suportă mai mult de 2 transferuri
simultane spre acelaşi server (recent Firefox a mărit la 6 această limită).

Lab2

Urmatorul exemplu numara cate cereri HTTP se fac dinspre browser spre server, in sesiunea curenta.

Pagina client:

<html>

<head>

<script type="text/javascript">

var xhr

function modifica()

try
{

xhr = new ActiveXObject("Msxml2.XMLHTTP")

} catch (e)

try

xhr = new ActiveXObject("Microsoft.XMLHTTP")

} catch (e)
Browser sniffing pt IE si Mozilla
{

xhr = false

}
Browser sniffing pt IE si Mozilla
Trimiterea variabilelor a si b spre
script.php
if (!xhr && typeof XMLHttpRequest !="undefined")

xhr = new XMLHttpRequest()

xhr.open("GET","numaratoare.php?a=1&b=2")

Am folosit o sintaxa alternativa, cu o


xhr.onreadystatechange=function() functie ANONIMA
{

if (xhr.readyState != 4) return;

document.getElementById("mesaj").innerHTML+="<br>"+ xhr.responseText

}
xhr.send(null)
Daca raspunsul e ok (readystate=4)
} se adauga raspunsul la continutul
existent al DIVului "mesaj" (pe un
</script>
DIV gol rezervat pt raspunsul rand nou, vezi <br>-ul)
</head> serverului
Obs: testul de status 200 lipseste, pt
a scurta exemplul

<body>

<div id="mesaj"></div>

<button onmouseover="modifica()">Click Me</button>

</body>

</html>
Declansarea comunicarii asincrone
- e specificat evenimentul
(MouseOver)
- e specificata functia ce instantiaza
XHR si se ocupa de schimbul de date

Scriptul server (trebuie sa aiba numele numaratoare.php si sa se afle in acelasi folder cu pagina client,
de exemplu htdocs!)

<?php

session_start();

if (isset($_SESSION["a"])) //se verifica daca exista variabila in sesiune; daca da, se incrementeaza

$_SESSION["a"]++;

else $_SESSION["a"]=1; //daca nu exista, variabila se initializeaza cu 1

print "Aceasta e conectarea nr.".$_SESSION["a"]."din sesiunea curenta<br/>";

print " Datele sosite la server sunt: ".$_SERVER["QUERY_STRING"]."<br/>";


?>

Observatii:

 Scriptul numara dialogurile care au loc intre client si server (la primul schimb creeaza o variabila
in sesiune, apoi o tot mareste la fiecare cerere HTTP);

 Pe langa textul care indica numarul conectarii asincrone, se returneaza si datele sosite la server,
citite direct din $_SERVER["QUERY_STRING"] (puteau fi luate si una cate una din $_GET["a"] sau
$_GET["b"]);

 Atentie, numaratoarea continua chiar si dupa Refresh scriptului (datorita sesiunii), dar si dupa
restartarea browserului! Si chiar dupa restartarea Apacheului!

Cum e posibil asta in conditiile in care variabilele sesiune sunt in mod implicit stocate pe server si
sesiunea se distruge automat la inchiderea browserului sau oprirea serverului?

Versiunile recente ale browserelor au implementat un mecanism automat de a transfera sesiunea intr-
un cookie, la momentul opririi browserului. La repornirea browserului, acesta isi recupereaza sesiunea
din cookie! Evident ca nici resetarea serverului nu afecteaza acest proces.

Asadar, daca dorim totusi sa distrugem sesiunea, avem variantele:

- sa punem in codul PHP comanda session_destroy() dupa un anumit numar de schimburi de date;

- sa stergem manual cookieul in care browserul transfera continutul sesiunii (cookieul creat de localhost,
in Firefox, cu Privacy – remove individual cookies – search dupa localhost si Remove Cookie cand e gasit);

- sa dezactivam din browserul optiunea de salvare automata a sesiunii (in Firefox, General – When
Firefox Starts show my windows and tabs from last time se va inlocui cu Show a blank page).

Practic, dacă nu luăm nici una din aceste măsuri, nu mai este nici o diferenţă (de comportament) între
sesiune şi cookie.
Urmăriți consola Firebug în timpul transferului:

Extindeţi scriptul PHPla varianta:

<?php

session_start();

if (isset($_COOKIE["b"]))

setcookie("b",$_COOKIE["b"]+1,time()+3600);

print "<b>Aceasta e revenirea cu nr.".$_COOKIE["b"]." a acestui calculator<b><br>";

else

setcookie("b",1,time()+3600);
print "<b>Aceasta e prima conectare a acestui calculator<b><br>";

if (isset($_SESSION["a"]))

$_SESSION["a"]++;

else

$_SESSION["a"]=1;

print "Aceasta e conectarea nr.".$_SESSION["a"]." din sesiunea curenta<br>";

print " Datele sosite la server sunt: ".$_SERVER["QUERY_STRING"]."<br>";

if ($_SESSION["a"]==10)

session_destroy();

?>

De data aceasta se fac 2 numărători în paralel: una prin cookie, care măsoară de câte ori a revenit
acelaşi calculator; una prin sesiune, care măsoară dialogurile din sesiunea curentă. Sesiunea e resetată
automat la 10.

Cadre invizibile prin tehnica Pull

<html>
Modificarea dinamica a atributului SRC
<head> al cadrului, pentru ca acesta sa
acceseze scriptul server vizat. URLul
<script type="text/javascript"> scriptului are atasate date de tip GET,
deci modificarea lui SRC are ca efect si
function trimite(cod) trimiterea datelor!

cadru=document.getElementById("cadruint")

cadru.src="codpostal.php?CodPostal="+cod
}

function citestedate(cdr)

{ Extragerea continutului cadrului

documentcadru=cdr.contentWindow.document

raspuns=documentcadru.body.innerHTML Extragerea continutului lui BODY din


continutul cadrului
procesare(raspuns)
(liniile cu rosu sunt nucleul tehnicii pull
– pagina mare trage datele din cadru)
}

function procesare(rasp)

vector=rasp.split(",") Procesarea raspunsului si includerea sa


in pagina (in formular)
document.getElementById("TXoras").value=vector[0]

document.getElementById("TXjudet").value=vector[1]

document.getElementById("TXadresa").value=vector[2]

</script>

</head>

<body>

<iframe id=cadruint width=400 height=50 src="" onload="citestedate(this)">

</iframe> Crearea cadrului:

<h1>Introduceti datele</h1>  pt a-l face invizibil, ar trebui


dimensiuni zero, dar l-am lasat
<form > vizibil pentru a-i putea monitoriza
continutul; SRC initial e vid;
<table>
 are ev. Onload, care apeleaza
functia de extragere a datelor in
momentul in care au sosit date in
cadru (de la server)
<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="trimite(this.value)"></td>

</tr>

<tr>
Declansarea transferului prin
<td>Adresa</td> evenimentul Blur

<td><input type=text id=TXadresa size=50></td>

</tr>

<tr>

<td>Oras</td>

<td><input type=text id=TXoras ></td>

</tr>

<tr>

<td>Judet</td>

<td><input type=text id=TXjudet ></td>

</tr>

<tr>

<td></td>

<td><input type=submit value=Trimite ></td>

</tr>
</table>

</form>

</body>

</html>

Scriptul server (salvat cu numele codpostal.php):

<?php

if ($_GET["CodPostal"]==400451)

print "Cluj Napoca,Cluj, Aleea Azuga...(completati detaliile)";

else

print "cod incorect, cod incorect, cod incorect";

?>

De urmarit si ce se intampla in Firebug:

A se observa:
 cadrul nu a fost facut invizibil (pentru a vedea in clar raspunsul serverului);

 in Firebug nu se mai poate folosi rubrica Console (aceasta monitorizeaza doar obiectul XHR) dar se poate folosi
rubrica Net (care monitorizeaza traficul dintre client si server, oferind toate detaliile schimbului de date)

 spre deosebire de XHR, in corpul HTML trebuie tratate doua evenimente:

o onLoad (aplicat cadrului, pentru a apela functia de procesare a raspunsului atunci cand raspunsul a
sosit); in varianta XHR, partea aceasta era inlocuita de handlerul onReadyStateChange;

o onBlur – evenimentul ce declanseaza manipularea atributului SRC (deci trimiterea datelor)

Cadre invizibile prin metoda Push

Pagina client:

<html>

<head>

<script type="text/javascript">

function trimite(cod)
Trimiterea datelor, prin manevrarea
{
atributului SRC al cadrului
cadru=document.getElementById("cadruint")

cadru.src="raspunspush.php?CodPostal="+cod

function procesare(rasp)

{
Procesarea raspunsului (desi pagina
vector=rasp.split(",") client nu contine nici un apel al acestei
functii!)
document.getElementById("TXoras").value=vector[0]

document.getElementById("TXjudet").value=vector[1]

document.getElementById("TXadresa").value=vector[2]

</script>
Cadrul (fara onLoad si fara functie de
</head> extragere a datelor!)

<body>

<iframe id=cadruint width=400 height=50 src="">

</iframe>

<h1>Introduceti datele</h1>

<form >

<table>

<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="trimite(this.value)"></td>

</tr>

<tr>

<td>Adresa</td>

<td><input type=text id=TXadresa size=50></td>

</tr>

<tr>

<td>Oras</td>

<td><input type=text id=TXoras ></td>

</tr>

<tr>
<td>Judet</td>

<td><input type=text id=TXjudet ></td>

</tr>

<tr>

<td></td>

<td><input type=submit value=Trimite ></td>

</tr>

</table>

</form>

</body>

</html>

Scriptul server (raspunspush.php)

<?php

if ($_GET["CodPostal"]==400451)

$raspuns="Cluj Napoca,Cluj, Aleea Azuga...(completati detaliile)";

else

$raspuns="cod incorect, cod incorect, cod incorect";

print "<html><head><script>

function impingedate()

continutcadru=document.body.innerHTML

parent.procesare(continutcadru)
}
Liniile cu rosu sunt nucleul tehnicii
</script> push: codul generat pentru a umple
cadrul se ocupa:
</head> - de impingerea datelor spre pagina
parinte la momentul onLoad (la sosirea
<body onload=impingedate()>" datelor in cadru)
- de apelarea functiei de procesare din
.$raspuns.
pagina parinte
"</body></html>";

?>

Observatii:

 in pagina client nu exista nici un eveniment si nici un apel explicit al functiei de procesare a
raspunsului (procesare()); deoarece procesarea e declansata prin remote scripting (JavaScript
generat din PHP!)

 serverul nu trimite doar date, ci si scriptul client (JavaScript) ce se ocupa de impingerea datelor
pe care le insoteste inspre pagina principala!

Lab3

Exemplu. AjaxRequest (Protoype) şi optimizări sintactice

Următorul exemplu rezolvă din nou scenariul cu codul poştal, de data aceasta folosind mecanismul
Ajax.Request şi clasa Element, oferite de frameworkul Prototype.

Pentru aceasta, downloadați mai întâi frameworkul Scriptaculous (care conține și Prototype în folderul
lib): http://script.aculo.us/downloads. Dați folderului numele scriptaculous și copiați-l în htdocs.

<html>
Acum eroarea este
inserată și stilizată cu
ajutorul clasei Element!!
(data trecută am făcut-o
cu inserare de cod,
folosind innerHTML)
<head>

<script type="text/javascript" src="scriptaculous/lib/prototype.js">

</script>

<script>

function proceroare(obiectxhr)

Element.update("Eroare","Mesaj eroare:"+obiectxhr.responseText)

Element.setStyle("Eroare",{color:'green'})

function procesareraspuns(obiectxhr)

{
$ este o versiune optimizată a lui
document.getElementById()

raspuns=obiectxhr.responseText setValue e o versiune specializată (pentru


elemente INPUT) a metodei setAttribute din
vector=raspuns.split(',') JavaScript.

for (i=0;i<vector.length;i++) Atenție! Avem în Prototype și funcția $F() care


ne returnează valoarea unui INPUT, dar este
{ read-only! Modificarea de valoare se face cu
setValue
camp=$('desubstituit'+i)

camp.setValue(vector[i])

function date(cod)
{

configurari={method:"get",parameters:"CP="+cod,onSuccess:procesareraspuns,onFailure:proceroare}

new Ajax.Request("codpostal.php",configurari)

}
Configurarea XHR e realizată în sintaxa JSON, cu
cele 2 evenimente, onSuccess şi onFailure,
pentru care s-au pregătit funcţii.
</script>

</head>

<body>

<h1>Introduceti datele</h1>

<form action="destinatie.php" method="post">

<table>

<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="date(this.value)"></td>

</tr>

<tr>

<td>Adresa</td>

<td><input type=text id=desubstituit0 size=50></td>

</tr>

<tr>

<td>Oras</td>
<td><input type=text id=desubstituit1></td>

</tr>

<tr>

<td>Judet</td>

<td><input type=text id=desubstituit2 ></td>

</tr>

<tr>

<td></td>

<td><input type=submit value=Trimite ></td>

</tr>

</table>
câmpurile vizate au primit IDuri similare, care să
</form> poată fi parcurse printr-un ciclu FOR

<p id=Eroare></p>

</body>

</html>

Următorul exemplu e similar, dar foloseşte alte facilităţi Prototype: o funcţie iteratoare, $$, generarea
erorii cu Element.insert.

<html>

<head>
Funcția ce se va aplica fiecărui
<script type="text/javascript" src="scriptaculous/lib/prototype.js"> element dintr-un vector (vectorul
INPUT-urilor în care inserăm
</script> valori).
<script>

function substituie(x,pozitie)

{ Acum eroarea este inserată cu


Element.insert (O altă variantă
x.setValue(vector[pozitie])
este să se creeze un DIV ascuns şi
să fie activat cu Elemen.show sau
}
Element.toggle)

function proceroare(obiectxhr)

Element.insert('formular',{after:'<div style="color:green"> Mesaj


Acum câmpurile sunt găsite pe
eroare:'+obiectxhr.responseText+'</div>'}) bază de CLASS şi sunt trecute toate
prin funcţia de substituire a valorii,
}
cu ajutorul funcţiei iteratoare
each!

function procesareraspuns(obiectxhr) Important: each transferă 2 valori


spre funcția-argument substituie:
{ elementul și poziția sa în vector
(vezi argumentele cu care s-a creat
raspuns=obiectxhr.responseText substituie())

vector=raspuns.split(',')

desubstituit=$$('.desubstituit')

desubstituit.each(substituie)

function date(cod)

config={method:"get",parameters:"CP="+cod,onSuccess:procesareraspuns,onFailure:proceroare}

new Ajax.Request("codpostal.php",config)

}
Acum formularul are ID,
</script> necesar la
Element.insert, pentru a
</head> şti DUPĂ ce element să
se facă inserarea!

<body>

<h1>Introduceti datele</h1>

<form action="destinatie.php" method="post" id="formular">

<table>

<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="date(this.value)"></td>

</tr>

<tr>

<td>Adresa</td>

<td><input class=desubstituit type=text size=50 /></td>

</tr>

<tr>

<td>Oras</td>

<td><input class=desubstituit type=text /></td>

</tr>
<tr>

<td>Judet</td>

<td><input class=desubstituit type=text /></td>

</tr>

<tr>

<td></td>
Nu am mai rezervat un
<td><input type=submit value=Trimite ></td> DIV pentru inserarea
erorii, el este CREAT de
</tr> Element.insert (în timp
ce Element.update îi
</table> substituia doar
conţinutul!)
</form>

</body>

</html>

Următorul exemplu este similar, dar foloseşte Ajax.Updater:

Pagina client

<html>

<head>

<script type="text/javascript" src="scriptaculous/lib/prototype.js">

</script>

<script type="text/javascript">

function proceroare(obiectxhr)
{

document.getElementById("Eroare").innerHTML="<font
color=red>Eroare"+obiectxhr.responseText+"</font>"
Marcatorul al carui continut se
}
substituie
function date(cod)

new Ajax.Updater("t1","sursa.php",

method: "get",

parameters: "CodPostal="+cod,

onFailure: proceroare

})

}
Fara onSuccess! Continutul e inserat
automat!

</script> (onSuccess e necesar doar daca vrem


sa se intample si altceva)
</head>

<body>

<h1>Introduceti datele</h1>

<form>

<table id=t1>

<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>
<td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="date(this.value)"></td>

</tr>

<tr>

<td>Adresa</td>

<td><input type=text id=TXadresa size=50></td>

</tr>

<tr>

<td>Oras</td>

<td><input type=text id=TXoras ></td>

</tr>

<tr>

<td>Judet</td>

<td><input type=text id=TXjudet ></td>

</tr>

<tr>

<td></td>

<td><input type=submit value=Trimite ></td>

</tr>

</table>

</form>

<p id=Eroare></p>

</body>

</html>

Scriptul server (salvat ca sursa.php)


<?php

if ($_GET["CodPostal"]==400451)

$oras='Cluj Napoca';

$judet='Cluj';

$adresa='Aleea Azuga....completati detaliile';

else

$oras='cod incorect';

$judet='cod incorect';

$adresa='cod incorect';

}
Serverul rescrie integral conţinutul
$cod=$_GET["CodPostal"]; tabelului, cu noile date inserate
(parţial redundant!)

print "<tr>

<td>Nume</td>

<td><input type=text id=TXnume ></td>

</tr>

<tr>

<td>CodPostal</td>

<td><input type=text id=TXcodpostal value='$cod' onblur='date(this.value)'></td>

</tr>

<tr>
<td>Adresa</td>

<td><input type=text id=TXadresa size=50 value='$adresa'></td>

</tr>

<tr>

<td>Oras</td>

<td><input type=text id=TXoras value='$oras'></td>

</tr>

<tr>

<td>Judet</td>

<td><input type=text id=TXjudet value='$judet'></td>

</tr>

<tr>

<td></td>

<td><input type=submit value=Trimite ></td>

</tr>";

?>

Obs:

1. Ajax.Updater nu necesita definirea unei functii de procesare a raspunsului (raspunsul e inserat


automat, se poate insa crea o functie de procesare daca se doreste ca raspunsul sa sufere modificari
inainte de inserare).

2. Exemplul este ineficient deoarece realizeaza refresh redundant (se reincarca tot continutul tabelului,
inclusiv marcatori care exista deja in pagina client!)

 De ce această redundanţă? Pentru că nu putem substitui doar câteva rânduri de tabel! Ele
ar trebui să fie cuprinse într-un DIV pe care să-l folosim ca ţintă pentru Updater dar…
marcatorul TABLE avea ca noduri fiu DIVuri (doar TR!) – orice nod-fiu nepermis al lui TABLE
este împins în afara tabelului!
Sarcina: Modificati exemplul pentru a minimiza refreshul redundant! (sa vina de la server doar cele 3
campuri ce se actualizeaza):

Variante de soluţii:

1. executăm de câte un Ajax.Updater pentru fiecare celulă (TD) al cărei conţinut se modifică;

2. nu mai încadrăm formularul într-un tabel, ci într-o structură de DIVuri care să arate ca un tabel
(astfel încât să putem defini un DIV convenabil care să substituie doar câmpurile ce suferă
modificări)

3. celulele ale căror câmpuri se substituie se grupează într-o singură celulă (ROWSPAN) pe care o
folosim ca ţintă (îi dăm ID) pentru Updater.

Exemplu PeriodicalUpdater

<html>

<head>

<script type="text/javascript" src="scriptaculous/lib/prototype.js">

</script>

<script>

function initializare()

config={method:"get",frequency:1,decay:2}

perup=new Ajax.PeriodicalUpdater("scor","sursascoruri.php",config)

}
</script>

</head>

<body onload="initializare()">

<div>Steaua <span id="scor"> 0 - 0 </span>Dinamo</div>

<input type="button" value="Reporneste updaterul periodic" onclick="perup.start()"/>

<input type="button" value="Opreste updaterul periodic" onclick="perup.stop()"/>

</body>

</html>

Script server, salvat cu numele sursascoruri.php:

<?php

print " 0 - 0 ";

?>

Mod de utilizare: - la încărcarea paginii încep updateurile periodice (puteţi să le urmăriţi în Firebug). Vor
deveni tot mai rare datorită decayului de 2, care dublează intervalul dintre două updateuri.

La un moment dat intraţi în scriptul server şi scrieţi 1-0 în loc de 0-0. După câteva secunde, noul scor
apare în pagină şi decayul se resetează (cererile devin din nou dese).

Cu cele două butoane puteţi opri sau reporni updaterul periodic.


Modificăm exemplul pentru a folosi şi 2 evenimente:

- onSuccess execută operaţii suplimentare la FIECARE update;

- onComplete se execută doar la oprirea updaterului.

<html>

<head>

<script type="text/javascript" src="scriptaculous/lib/prototype.js">

</script>

<script>

function finalizare()

alert('Se opreste updaterul')

function confirmare()

alert('Tocmai a avut loc un update')

function initializare()

config={method:"get",frequency:1,decay:2,onSuccess:confirmare,onComplete:finalizare}

perup=new Ajax.PeriodicalUpdater("scor","sursascoruri.php",config)
}

</script>

</head>

<body onload="initializare()">

<div>Steaua<span id="scor"> 0 - 0 </span>Dinamo</div>

<input type="button" value="Reporneste updaterul periodic" onclick="perup.start()"/>

<input type="button" value="Opreste updaterul periodic" onclick="perup.stop()"/>

</body>

</html>

Lab 4

Comportamente preprogramate in Scriptaculous –

Clonare spațială, Autocompleter, Slider, InPlaceEditor

Clonarea spațială (de poziție și dimensiuni)

Un instrument important în Prototype este clonarea spațială, necesară în coordonarea poziționării și


dimensionării unor elemente în funcție de altele.

Scriptaculous, pe lângă că oferă funcția de clonare, se și bazează pe aceasta în alte funcții, cum ar fi cele
legate de Autocompleter (pentru poziționarea listei de sugestii imediat sub textboxul în care se face
căutarea).
În general clonarea spațială se folosește în 3 moduri:

 pentru a plasa un element în locul altuia (care a fost ascuns);

 pentru a plasa un element în dreptul altuia (lângă, dedesubt, deasupra etc.) prin clonare parțială
(cu un anumit decalaj, limitat la o axă);

 pentru a dimensiona un element în funcție de altul.

E important de stăpânit operația de clonare, de cunoscut problemele pe care le creează browserele și


modul în care le putem depăși.

De aceea e recomandat ca:

 paginile să se creeze cu o declarație <!DOCTYPE html> pe prima linie, ceea ce face browserele să
intre în modul “standard” și să dea rezultate mult mai uniforme (dimensionarea și poziționarea
sunt printre operațiile cele mai afectate de diferențele dintre browsere, iar prezența unei
declarații DOCTYPE face să dispară mare parte din diferențe);

 funcția clonePosition să fie folosită pe elemente fără border și padding, altfel apar diverse
decalaje ce pot necesita ajustări manuale.

Pentru a sesiza mai clar efectele, vom crea 2 DIVuri de dimensiuni și culori diferite, cu chenare pentru a
le putea vedea clar limitele.

<!DOCTYPE html>

<html>

<head>

<style>

#d1 {width:200px;height:100px;color:red;border:2px solid red}

#d2 {width:100px;height:50px;color:blue;border:2px solid blue}

</style>

<script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>


<script>

function cloneaza()

Element.clonePosition('d2','d1')

</script>

</head>

<body>

<input type="button" value="click aici pt clonare de pozitie" onclick="cloneaza()"/>

<div id="d1">Acesta e primul div</div>

<div id="d2">Acesta ii va clona pozitia primului</div>

Text oarecare

</body>

</html>

În această formă de bază, clonePosition nu face decât să cloneze dimensiunile: al doilea DIV se mărește
până la dimensiunile primului (aproximativ, căci apare o abatere dată de grosimea chenarului celui de-al
2-lea DIV; dacă ar avea și un padding, abaterea ar fi chiar mai mare – ne vom ocupa mai târziu de
eliminarea manuală a acestei abateri).

 Pentru a clona atât dimensiunile cât și poziția, e necesar ca elementul care primește noua
poziție să fie scos din fluxul natural al textului, deci să fie definit cu poziționare ABSOLUTĂ sau
FIXĂ!

 Poate fi definit și cu poziționare RELATIVĂ, care se face relativ la poziția curentă (deci e utilă
dacă vrem să mutăm un element sincron cu alt element, dar păstrând distanța relativă dintre
ele).

Aplicăm poziționare absolută pe DIVul ce va fi manipulat:


#d2 {width:100px;height:50px;color:blue;border:2px solid blue;position:absolute;top:200px;left:200px}

Avertisment: prin poziționare absolută, DIVul este scos din fluxul natural al textului și locul pe care îl
ocupa este invadat (aici de „Text oarecare”, care urmează sub div). DIVul este mutat la coordonatele
precizate unde va acoperi eventualul conținut din zona respectivă. De aceea elementele cu poziționare
absolută de obicei sunt elemente ce apar temporar peste conținutul paginii (deci au un background
definit, de ex. casete de dialog cu afișare temporară). Uneori sunt inițial ascunse și sunt făcute vizibile din
când în când pentru a afișa diverse mesaje sau conținut. În general elementele care se mișcă mult în
pagină se definesc cu poziționare absolută pentru a nu fi influențate de restul paginii.

În urma poziționării absolute, se vede că butonul de clonare aplică acum și clonare de poziție dar... cu o
abatere neplăcută.

Această abatere e prezentă din cauză că browserele impun implicit o bordură pe marginea paginii, de
care clonarea poziției nu ține cont. Soluția pentru evitarea abaterii este una dintre următoarele:

 anularea oricărei margini pentru BODY: <body style=”margin:0px”>

 poziționarea relativă a lui BODY: <body style=”position:relative”>

Acum clonarea ar trebui să funcționeze corect.

Clonarea parțială

Mai frecventă decât clonarea totală a poziției, este necesitatea de a poziționa un element în dreptul
altuia. Pentru aceasta, clonePosition ne permite să impunem un decalaj suplimentar:

Element.clonePosition('d2','d1',{offsetLeft:200})

... aplicând un decalaj al marginii stângi cu o valoare egală cu lățimea primului DIV, al doilea va fi
poziționat la dreapta primului

Element.clonePosition('d2','d1',{offsetTop:100})
...aplicând un decalaj al marginii de sus cu o valoare egală cu înălțimea primului DIV, al doilea va fi
poziționat dedesubt

Obs:

 Pentru deasupra și la stânga, dăm valori negative celor 2 decalaje;

 Dacă dorim să poziționăm colț-în-colț, combinăm ambele decalaje;

 Dacă trebuie neapărat să avem chenare vizibile și micile lor suprapuneri sunt supărătoare, vom
adăuga și grosimea chenarelor la decalaj: 204, respectiv 104 în aceste exemple (dublul grosimii
lui border); la fel, dacă primul DIV are padding, îl vom adăuga și pe acesta la decalaj (nu și
bordura margin, care nu e inclusă în dimensiunea elementelor!).

Alte opțiuni:

Element.clonePosition('d2','d1',{setTop:false})

...clonează doar poziționarea orizontală, pe verticală păstrează poziția curentă

Element.clonePosition('d2','d1',{setLeft:false})

...clonează doar poziționarea verticală, pe orizontală păstrează poziția curentă

Element.clonePosition('d2','d1',{setWidth:false})

...clonează totul în afară de lățime

Element.clonePosition('d2','d1',{setHeight:false})

...clonează totul în afară de înălțime

Combinând aceste opțiuni, putem clona fie doar dimensiunile, fie doar poziția, fie diverse combinații
între ele limitate la axa X sau axa Y.

Influența chenarelor și bordurilor


S-a văzut la clonarea cu decalaj că în decalaj ar trebui inclusă și grosimea chenarelor și paddingului, când
acestea există în primul DIV, căci clonarea nu ține cont de ele. În exemplul precedent am sugerat să se
calculeze manual dimensiunea totală (204), adunând eventualul chenar sau padding (2*border=4) la
dimensiunea declarată (200).

E mai eficient dacă realizăm acest calcul tot prin programare, mai ales dacă există șanse ca aceste valori
să se schimbe dinamic.

Modificați stilul primului DIV, cu padding de 10:

#d1 {width:200px;height:100px;color:red;border:4px solid red;padding:10px}

De data aceasta clonarea cu offset de 200 sau 100 va fi mult mai deranjantă, căci trebuie să adunăm și
grosimea paddingului. Pentru a ne scuti de astfel de calcule, Prototype ne oferă funcțiile
getWidth/getHeight care ne returnează dimensiunile calculate ale elementelor (incluzând border și
padding!)

Modificăm funcția de clonare pentru a utiliza dimensiunile calculate în locul celor declarate:

function cloneaza()

latime=Element.getWidth('d1')

Element.clonePosition('d2','d1',{offsetLeft:latime})

Dacă în acest caz abaterile s-au rezolvat simplu, în cazul clonării de dimensiune, suntem nevoiți să facem
ajustări mai detaliate, căci clonePosition nu ne oferă opțiune de aplicare a unei abateri de dimensiune și
trebuie să manipulăm direct CSSul.
Adăugați un padding și la al doilea DIV,ceea ce va face ca abaterea de dimensiune să fie și mai puternică:

#d2 {width:100px;height:50px;color:blue;

border:2px solid blue;position:absolute;top:200px;left:200px;padding:5px}

Mai exact, abaterea este 2*(border+padding), deci 14 pixeli. Deci dacă vrem ca al doilea DIV să
primească exact dimensiunile primului, indiferent dacă are border și padding, vom aplica o ajustare
manuală. Ajustarea este indicată prin comentarii în codul funcției de clonare (a doua parte a funcției):

function cloneaza()

//plasarea alaturata a celor 2 divuri

latime=Element.getWidth('d1')

Element.clonePosition('d2','d1',{offsetLeft:latime})

//citirea inaltimii calculate a primului div (cu padding si border)

inaltime1=Element.getHeight('d1')

//citirea grosimii chenarului

chenar=Element.getStyle('d2','border-top-width')

//citirea grosimii paddingului

pad=Element.getStyle('d2','padding-top')

//calcularea inaltimii de declarat pt div 2, ca sa ajunga la inaltimea calculata a divului 1

//functia parseInt e necesara caci functiile de mai sus returneaza valorile ca string, nu ca numere

inaltime2=parseInt(inaltime1)-2*parseInt(chenar)-2*parseInt(pad)

//declararea noii inaltimi (care adunata cu bordurile va da inaltimea calculata dorita)

Element.setStyle('d2',{height:inaltime2+'px'})
}

Autocompleter

Autocompleter se folosește de obicei în corelație cu un textbox, când, în timpul completării acestuia,


utilizatorul primește sugestii privind datele pe care să le tasteze. De obicei mecanismul e folosit la
motoare de căutare. Sugestiile sunt afișate imediat sub textbox și utilizatorul poate alege o sugestie
pentru a nu o mai tasta. Sugestiile pot să vină de la server (ex: o bază de date cu căutările care s-au mai
făcut de către alți utilizatori) sau să fie generate în JavaScript (ex: un cookie cu căutările pe care le-a mai
făcut utilizatorul curent).

Pasul1. Pregătim câmpul text și zona de afișare a sugestiilor.

<!DOCTYPE html>

<html>

<head>

<style>

div#sugestii

{width:250px;border:1px solid red}

div#sugestii ul

{border:1px solid blue}

div#sugestii ul li.selected

{background-color:#ffb;}

div#sugestii ul li

{border:1px solid green;cursor:pointer;}


</style>

</head>

<body>

Caseta de cautare:<br/>

<input type="text" id="tbox"/>

<div id="sugestii">

<ul>

<li class="selected">prima sugestie</li>

<li>a doua sugestie</li>

<li>a treia sugestie</li>

</ul>

</div>

</body>

</html>

 Am creat un textbox cu IDul tbox (în mod normal e nevoie și de un buton care să declanșeze
căutarea pe server, dar îl vom neglija căci nu dorim să implementăm căutarea efectivă ci doar
generarea sugestiilor).

 Imediat după el am creat un DIV rezervat pentru afișarea sugestiilor, cu IDul “sugestii”.

 În DIVul de sugestii am inserat o listă neordonată. Important: Autocompleterul din


Scriptaculous este preprogramat astfel încât să aștepte sosirea de la server a unei liste UL
(chiar dacă serverul nu găsește nici o sugestie, el tot va trebui să returneze un marcator UL, fie
și unul gol!) Din acest motiv, în această fază ne formatăm sugestiile sub forma unei liste-locțiitor
(ca și cum aceasta ar fi venit deja de la server), apoi vom șterge lista păstrând doar formatările.

 Unul din elementele listei are class=selected. Și acest lucru este impus de Scriptaculous!
Scriptaculous va genera automat acest atribut pe elementul deasupra căruia se află mouseul
deci trebuie să-i pregătim o formatare distinctă de restul listei.

 Am construit câteva stiluri, unele doar provizoriu:


o Am pus chenare (border) la DIVul sugestii si elementele sale pentru a vedea mai clar ce
modificari de poziționare trebuie să le aducem;

o Am modificat culoarea elementului presupus activ (cu class=select) din listă;

o Am modificat tipul cursorului la trecerea mouseului peste elementele listei.

Verificați cum arată în acest moment pagina. În ce privește poziționarea sugestiilor imediat sub textbox,
avem două variante de lucru:

 Să ne bazăm că Autocompleter va calcula corect poziționarea (prin clonarea de care am discutat


înainte);

 Să definim noi poziționarea (grupând textboxul și sugestiile în același DIV, să le putem manevra
ca un întreg) și să dezactivăm poziționarea automată oferită de Autocompleter.

Pasul2. Formatăm lista de sugestii pentru a putea fi afișată sub forma unui meniu.

În acest moment lista de sugestii trebuie să sufere câteva formatări:

 să dispară bulinele de la elemente (list-style-type:none);

 să dispară spațiul liber din jurul listei și din fața elementelor listei (acest aspect este mai
problematic, căci e tratat diferit de browsere – la unele acest spațiu liber e controlat cu margin,
la altele cu padding, deci va trebui să punem ambele proprietăți la zero);

 când totul e poziționat cum trebuie, eliminăm chenarele.

Toate aceste modificări se aplică în momentul în care modificăm stilul CSS al listei, astfel:

div#sugestii ul

list-style-type:none;

margin:0px;

padding:0px;
}

Și eliminăm chenarul de la elementele listei:

div#sugestii ul li

{ cursor:pointer}

Chenarul DIVului îl lăsăm vizibil.

Pas3. Creăm scriptul server provizoriu

În această fază creăm un script foarte simplu, care să returneze o listă oarecare, pentru a ne convinge că
mecanismul funcționează. Ulterior vom extinde scriptul pentru a returna doar elemente care încep cu
literele tastate în textbox.

<?php

print '<ul><li>prima sugestie</li><li>a doua sugestie</li><li>a treia sugestie</li></ul>';

?>

Salvăm fișierul cu numele sugestii.php. Observați că scriptul nu returnează și atributul class=selected;


acesta e generat AUTOMAT de Scriptaculous la MouseOver!

Pas4. Introducem obiectul Autocompleter

Aici trebuie să ne alegem evenimentul la care să se creeze mecanismul Autocompleter. De obicei acesta
se creează la evenimentul ONLOAD, pentru a fi disponibil imediat după încărcarea paginii. Alternativ, ar
putea fi generat printr-un eveniment ONFOCUS sau ONCLICK (când se intră cu mouseul în textbox) sau
prin alte evenimente.
Adăugați mai întâi liniile de apel al funcțiilor Scriptaculous și Prototype (în HEAD):

<head>

<script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>

<script src="scriptaculous/src/scriptaculous.js" type="text/javascript"></script>

Apoi asociați crearea lui Autocompleter cu evenimentul ONLOAD din BODY:

<script>

function initializare()

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",{})

</script>

..............

<body onload=”initializare()”>

Argumentele constructorului sunt:

 tbox (IDul textboxului);

 sugestii (IDul DIVului formatat pentru afișarea sugestiilor);

 sugestii.php (scriptul ce trimite sugestiile);

 {} (obiect cu argumente opționale, în cazul de față fără opțiuni).

În plus, se poate șterge lista UL scrisă deja (era provizorie, doar cât să-i putem face stiluri; de aici încolo,
lista va fi inserată automat din sugestiile venite de la server!)
Corpul paginii va rămâne:

<body onload="initializare()">

Caseta de cautare:<br/>

<input type="text" id="tbox"/>

<div id="sugestii"></div>

</body>

Pas5. Poziționarea cu bug

După sosirea listei UL, Autocompleter aplică o clonare de poziție, pentru a plasa lista de sugestii imediat
sub textbox! Reamintim că la clonarea de poziție poate apare o mică abatere datorată marginii implicite
pe care browserele o pun în jurul întregii pagini.

O dezactivăm cu

<body onload="initializare()" style="margin:0px">

O altă metodă de depășire a bugului este să dezactivăm poziționarea automată oferită de Scriptaculous,
caz în care va trebui să facem manual poziționarea (să ne asigurăm că sugestiile și textboxul sunt
grupate în același DIV, unul după altul sau poziționat cu CSS). Dezactivarea clonării de poziție are loc
dacă DIVul de sugestii primește poziționare relativă (nu o faceți în acest exemplu, aplicați cealaltă
măsură):

<div id="sugestii" style="width:250px;border:1px solid red;position:relative">

Pas6. Modificarea scriptului server pentru a face căutare.


În acest moment, scriptul server livrează o listă oarecare. Această listă, însă, trebuie obținută dintr-o
sursă de date din care se selectează acele valori ce încep cu litera tastată în textbox!

În prima fază, inițializăm sursa de date cu valori oarecare:

<?php

$sursadate = array('Ana', 'Andrei', 'Alin','Bebe');

print '<ul>';

for ($i = 0; $i < sizeof($sursadate); $i++)

print '<li>'.$sursadate[$i].'</li>';

print '</ul>';

?>

Mai departe, în ciclul de parcurgere a sursei de date trebuie să punem un filtru – să se extragă doar
valorile care încep cu literele tastate în textbox. Pentru aceasta, trebuie să ne asigurăm că textboxul
trimite date la server, adăugându-i un atribut NAME:

<input type="text" id="tbox" name="litere"/>

Apoi, să capturăm această valoare la server (nu avem nevoie de Submit, Autocompleterul se ocupă de
trimiterea asincronă a conținutului din textbox, după fiecare tastă apăsată sau cu o frecvență pe care o
putem modifica prin opțiunile suplimentare de la instanțierea Autocompleter!):

Preluăm la server conținutul textboxului:

$litere=$_POST['litere'];
Construim o expresie regulată care să verifice dacă un string începe cu literele sosite prin POST (case-
insensitive):

$expreg="/^".$litere."/i";

Includem în script testarea expresiei regulate pentru fiecare element din vectorul de pe server:

<?php

$litere=$_POST['litere'];

$expreg="/^".$litere."/i";

$sursadate = array('Ana', 'Andrei', 'Alin','Bebe');

print '<ul>';

for ($i = 0; $i < sizeof($sursadate); $i++)

if (preg_match($expreg,$sursadate[$i])==1)

print '<li>'.$sursadate[$i].'</li>';

print '</ul>';

?>

Obs: Am avut nevoie de expresia regulată deoarece sugestiile sunt stocate într-un vector. Dacă avem
sugestiile într-o bază de date, în loc de expresia regulată vom folosi interogări cu LIKE. Asta ar însemna
să programați și căutarea efectivă.

Asta presupune:

 încadrarea textboxului într-un formular, care va avea un buton Search (de tip submit) și ca
destinație un script PHP care face două lucruri:

o căutarea efectivă dorită de utilizator;

o memorarea cuvântului căutat într-un tabel dedicat pentru cuvinte căutate; apoi, scriptul
sugestii.php va fi modificat să scoată sugestiile din acest tabel cu interogări LIKE

Vă rămâne acest mecanism ca sarcină de lucru.


Eventual, în tabelul dedicat căutărilor, se poate stoca și numărul de căutări făcute cu fiecare cuvânt,
pentru a se putea scoate doar sugestiile mai populare – primele câteva, cum face Google, căci ar fi foarte
neperformant să facă absolut toate sugestiile care încep cu o anumită literă.

E posibil să dorim ca fiecare sugestie să vină cu niște informații suplimentare care să-l ajute pe utilizator
în selectarea sugestiei dorite, FĂRĂ CA informațiile suplimentare să fie inserate în textbox la momentul
deciziei.

Dacă pur și simplu adăugăm text suplimentar în elementele listei...

print '<li>'.$sursadate[$i].'--Info suplimentar</li>';

...acesta va deveni parte din sugestie și va fi inserat în textbox, când utilizatorul își selectează sugestia
dorită. Pentru a preveni acest lucru, Autocompleter va neglija orice text din listă care apare într-un SPAN
cu CLASS=informal:

Extindem concatenarea din PHP astfel:

print '<li>'.$sursadate[$i].'<span class="informal"> --Info suplimentare</span></li>';

Partea din SPAN va fi afișată în lista de sugestii, dar nu va fi preluată la selectarea unei sugestii. Evident,
și acele informații suplimentare ar trebui preluate dintr-o sursă de date și nu concatenate în acest fel.

Opțiuni suplimentare:

1.Dacă estimăm că va dura mai mult căutarea sugestiilor, trebuie să oferim și un indicator grafic (un gif
animat, gen clepsidră) care să sugereze o stare de așteptare (cu indicator).
2.Dacă vrem ca schimbul de date să fie mai rar, putem preciza după câte caractere tastate să se inițieze
procesul Autocompleter (cu minChar) sau la câte secunde (cu frequency).

3. Dacă vrem să trimitem și alte date decât literele tastate, o putem face cu parameters.

4. Dacă vrem ca în același textbox să se tasteze mai multe valori separate cu un delimitator (ca la
introducerea mai multor adrese de e-mail), o putem face cu tokens.

Downloadați un gif animat sugestiv (căutați pe Google după spinner.gif). Salvați imaginea pe server cu
numele animatie.gif.

Inserați între textbox și DIVul cu sugestii imaginea. Faceți-o invizibilă și dați-i un ID:

<img id="asteapta" style="display:none" src="spinner.gif" alt="Asteapta..." />

Apoi modificați linia de creare a Autocompleterului, cu opțiunile suplimentare indicator (IDul imaginii) și
minChars (după câte caractere să apară sugestiile):

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",{indicator:"asteapta",minChars:2})'

O opțiune suplimentară importantă este și parameters – permite ca Autocompleter să trimită la server și


ALTE date decât literele din textbox. Exemplu:

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",

{indicator:"asteapta",minChars:2, parameters:”a=100&b=200”})'

Se poate verifica în Firebug, sau pe server că variabilele a și b sunt trimise.

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",


{indicator:"asteapta",minChars:2, tokens:[";"]})'

La tastarea în textbox a caracterului ; Autocompleterul se resetează și trimite la server doar literele ce


urmează după ;. Aceasta permite selectarea mai multor sugestii în același textbox, separate prin
delimitatorul ales.

Autocompleter mai oferă și 3 evenimente cărora li se pot asocia funcții personalizate:

 callback – funcția se va executa în locul comunicării asincrone (deci dacă o folosim va trebui să
implementăm explicit în această funcție comunicarea asincronă cu Ajax.Request); funcția
primește obligatoriu 2 argumente: numele textboxului și datele de trimis la server;

 updateElement – funcția se va executa la selectarea unei sugestii și va substitui mecanismul


automat de inserare a elementului selectat în textbox; funcția primește obligatoriu ca argument
elementul selectat;

 afterUpdateElement – funcția se va executa la selectarea unei sugestii, da NU va substitui


mecanismul de inserare (ci îl suplimentează); funcția primește obligatoriu 2 argumente:
textboxul și elementul selectat.

Adăugați funcția:

function f1(elem)

alert('Se insereaza textul:\n'+elem.innerHTML)

document.getElementById('tbox').value=elem.innerHTML

Aceasta realizează inserarea argumentului în textbox, dar are în plus și o afișare de mesaj. Pentru a-i
vedea efectul, adăugați evenimentul updateElement la opțiunile suplimentare:
new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",

{indicator:"asteapta",minChars:2, updateElement:f1})'

Așadar, printr-o astfel de funcție putem realiza operații suplimentare la momentul selectării unei
sugestii, dar are dezavantajul că trebuie să programăm manual inserarea sugestiei în textbox. În schimb
dacă programăm o funcție pentru evenimentul afterUpdateElement putem păstra mecanismul implicit
de inserare (cu diferența că funcția acestui eveniment trebuie să aibă 2 argumente – textboxul și
elementul selectat).

La fel se folosește și evenimentul callback, dar acesta substituie comunicarea asincronă:

Adăugăm în pagină funcția:

function f2(camp,qstring)

alert('Se trimite de la campul '+camp.name+' stringul '+qstring)

Și o asociem evenimentului:

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php",

{indicator:"asteapta",minChars:2, callback:f2})'>

Funcția afișează o alertă la momentul la care AR TREBUI să se realizeze comunicarea asincronă (după
tastarea a 2 caractere). Deci dacă personalizăm o astfel de funcție, trebuie să includem în ea și codul
comunicării asincrone, precum și inserarea listei de rezultate în DIVul de sugestii. Altfel spus, folosirea
funcției callback ne obligă să programăm manual aproape tot mecanismul Autocompleter.
Autocompleter local

E posibil ca lista de sugestii să nu vină de la server, ci să fie generată la nivelul clientului. În acest caz,
funcția de inițializare poate arăta astfel:

function initializare()

lista=['Ana','Andrei','Bebe','Bogdan']

new Autocompleter.Local("tbox", "sugestii", lista,{})

Constructorul nu se mai numește Ajax.Autocompleter, ci Autocompleter.Local și primește un argument


în plus – variabila vector din care se iau sugestiile. Asta presupune că sugestiile au fost generate local
printr-un mecanism oarecare:

 fie sugestii venite de la server pe alte căi decât prin Autocompleter (de ex: date venite ca XML,
JSON și mutate într-un vector cu ajutorul JavaScript);

 fie sugestii generate direct în JavaScript (de ex. căutările făcute de un utilizator, dar memorate în
cookieurile sale în loc să fie memorate în baza de date – asta înseamnă că fiecare utilizator va
primi doar sugestii legate de ce a mai căutat el înainte, nu și cele căutate de alți utilizatori -
adesea se practică o combinație între cele 2: Google ne oferă și o listă de căutări făcute de noi
(din cookie), dar și una făcută de restul lumii).

Pentru aceasta adăugăm și butonul care să declanșeze căutarea:

<input type="button" value="Cautare" onclick="memoreaza()"/>

Nu îi vom programa căutarea efectivă pe server, dar avem nevoie de el pentru a-i programa în JavaScript
crearea cookieului cu căutarea curentă:

function memoreaza()

{
//aici ar veni codul ce comunica cu scriptul server responsabil cu cautarea, pe baza de Ajax.Request

//dar in acest exemplu implementam doar memorarea cautarilor facute in cookie:

//pas1.dam cookieurilor nume de forma cautare1,cautare2 etc. ca sa nu se suprascrie

//pentru asta ne bazam pe variabila nrcookie cu care vom numara cookieurile existente

//(va trebui sa o si initializam, dar nu aici, ci la evenimentul ONLOAD!)

nrcookie++

numecookie='cautare'+nrcookie

//dam valoarea cookieului, preluata din textboxul cu cautarea facuta

valcookie='='+$F('tbox')

//fixam data de expirare

setaricookie="; expires=Thu, 2 Aug 2011 20:47:11 UTC"

//concatenam componentele cookieului si le memoram pe disc

document.cookie=numecookie+valcookie+setaricookie

Din comentarii se vede că am folosit variabila nrcookie, care nu există. În ea intenționăm să numărăm și
să numerotăm cookieurile, de aceea, la încărcarea paginii, primul lucru care trebuie făcut este să vedem
câte cookieuri există (dacă nu există nici unul, se va inițializa cu valoarea 1)

function initializare()

//extragem cookieurile existente într-un vector – pe disc ele sunt fișiere text cu valori delimitate de ;

toatecookie=document.cookie.split(';')

//aflăm câte cookieuri există – dacă nu e nici unul, lungimea implicită va fi 1, deci nu e necesar un test

nrcookie=toatecookie.length
//trecem vectorul de cookies printr-un iterator, pentru a-i extrage valorile

lista=toatecookie.collect(extrage)

new Autocompleter.Local("tbox", "sugestii", lista,{})

Următoarea funcție este cea care asigură extragerea valorilor (apelată de funcția iteratoare collect):

function extrage(elem)

return elem.split('=')[1]

Funcția primeșe de la iterator fiecare element al vectorului, și, bazându-se pe faptul că elementele au
forma nume=valoare, sparge perechea într-un alt vector și îi ia al doilea element (valoarea de după
egal).

Slider

Mecanismul Slider este la bază un grup de 2 DIVuri – unul lung (șina) și unul mic (mânerul care se trage
de-a lungul șinei). Pentru a simplifica poziționarea lor, ideal este ca:

 șina să conțină mânerul;

 fiecare să aibă lățime și înălțime convenabilă;

 fiecare să aibă un fundal sugestiv – în acest exemplu le vom colora fundalul ca să creeze
impresia de bară și mâner dar în practică li se pune pe background câte o imagine sugestivă
(PNG cu background transparent) folosind proprietatea background: url(……..).

<!DOCTYPE html>

<html>
<head>

<style>

#sina {background:red;width:200px;height:10px}

#maner {background:blue;width:10px;height:10px}

</style>

<script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>

<script src="scriptaculous/src/scriptaculous.js" type="text/javascript"></script>

<script>

function initializeaza()

new Control.Slider("maner","sina",{})

</script>

</head>

<body onload="initializeaza()">

<div id="sina">

<div id="maner"></div>

</div>

</body>

</html>

Observați dimensiunile convenabil setate – lungime mare pentru șină, cu înălțime mică,respectiv un
pătrățel mic, egal cu înălțimea șinei, pentru mâner. Repet, în proprietatea background se pot stoca și
imagini care să facă Sliderul mai estetic.

De obicei un Slider e folosit pentru a controla o gamă de valori posibile ce influențează alte elemente din
pagină. În cazul cel mai simplu, gama de valori va fi pur și simplu afișată într-un textbox:
<input id="valori" type="text"/>

Pentru a lega Sliderul de textbox, trebuie să definim o funcție pentru evenimentul onSlide, funcție ce
primește obligatoriu valoarea curentă a Sliderului – pe care noi trebuie să o transferăm prin cod mai
departe, spre textboxul cu ID=valori:

function initializeaza()

new Control.Slider("maner","sina",{onSlide:afiseaza})

function afiseaza(valoare)

$('valori').setValue(valoare)

Se poate vedea că:

 valorile generate de Slider sunt în intervalul 0-1 (putem modifica intervalul cu opțiunile range și
values);

 afișarea valorii în textbox se face doar când tragem mânerul (drag); ar trebui să se facă
actualizarea și când dăm un simplu click undeva pe șină (pentru aceasta avem evenimentul
onChange).

Modificați sliderul:

function initializeaza()

{
new Control.Slider("maner","sina",{range:$R(10,50),onSlide:afiseaza,onChange:afiseaza})

Acum valorile vor fi în intervalul 10-50 și e posibilă și parcurgerea Sliderului cu clickuri.

Obs:Alături de range putem folosi și values:[10,20,30,40,50] care va face ca valorile sliderului să fie
discrete (mânerul va sări din 10 în 10, nu va putea fi poziționat oriunde în interval).

Mai departe, adăugăm consecințe noi în pagină:

 un paragraf care să își schimbe fontul în funcție de slider;

 un paragraf care să își schimbe poziția verticală în funcție de slider.

Adăugăm cele 2 paragrafe:

<body onload="initializeaza()">

<div id="sina">

<div id="maner"></div>

</div>

<input id="valori" type="text"/>

<div id="tinta1">Acest text va fi redimensionat dinamic</div>

<div id="tinta2">Acest text va fi repozitionat dinamic</div>

</body>

Apoi modificăm funcția de legătură cu Sliderul, adăugând operații de ajustare a mărimii de font (pt
primul paragraf) și de ajustare a poziției verticale (pentru al doilea) în funcție de valoarea curentă:

function afiseaza(valoare)

{
$('valori').setValue(valoare)

Element.setStyle('tinta1',{fontSize:valoare+'px'})

Element.setStyle('tinta2',{position:'relative',top:valoare+'px'})

Observați:

 cum este concatenată valoarea Sliderului la proprietățile CSS;

 faptul că numele proprietăților CSS e puțin diferit: nu am folosit font-size, ci fontSize (deoarece e
acoladele acelea nu sunt cod CSS, ci cod JavaScript, iar în JavaScript toate proprietățile cu
cratimă în nume suferă această mică conversie);

 faptul că atât dimensiunea cât și poziția suferă modificări în intervalul 10-50.

Dacă dorim ca modificarea aplicată în pagină să nu fie EXACT modificarea Sliderului, ci o valoare
proporțională, înmulțim valoarea cu proporția dorită:

Element.setStyle('tinta1',{fontSize:valoare*0.5+'px'})

Element.setStyle('tinta2',{position:'relative',top:valoare*2+'px'})

Acum fontul va varia în intervalul 5-25, poziția în intervalul 20-100.

Dacă dorim să putem tasta în textbox o valoare și aceasta să mute Sliderul automat, avem nevoie de:

- Sliderul să primească un nume ca să-l putem referi din alte funcții:

sld=new Control.Slider("maner","sina",{range:$R(10,50),onSlide:afiseaza,onChange:afiseaza})

- atașăm textboxului o funcție care să se apeleze la apăsarea tasteri Enter


<input id="valori" type="text" onkeypress="seteaza(this.value,event)"/>

- definim funcția respectivă, în care testăm dacă tasta apăsată este Enter (codul 13):

function seteaza(v,e)

eveniment=e||window.event

if (eveniment.keyCode==13)

sld.setValue(v)

Observații:

 onkeypress este un eveniment ce se apelează indiferent ce tastă apăsăm; în consecință, trebuie


să-i dăm un al doilea argument, event, prin care se va putea verifica despre ce tastă e vorba;

 în interiorul funcției, nu e sigur că argumentul event ajunge! Internet Explorer nu recunoaște


acel argument, în schimb oferă obiectul window.event cu același rol. De aceea folosim expresia
eveniment=e||window.event, care va atribui variabilei eveniment fie argumentul event (dacă
există, deci în Firefox), fie obiectul window.event (dacă event nu a sosit, deci cazul IE);

 cu un IF verificăm proprietatea keyCode a evenimentului (Enter are codul 13), apoi cu setValue
aplicăm o setare de valoare Sliderului.

Codurile tuturor tastelor le găsiți aici:

http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-Codes-
Key-Codes.aspx

Tot acest mecanism cu tasta Enter poate fi ocolit:

 dacă folosim evenimentul onblur (dar atunci mutarea mânerului se va face la apăsarea lui Tab)
 dacă punem alături și un buton la apăsarea căruia să mutăm mânerul.

Totuși în practică e mai intuitiv ca valoarea tastată într-un textbox să fie confirmată printr-un Enter
simplu, mai ales că nu e vorba de un formular care să trebuiască trimis, deci n-are sens să creăm și un
buton.

În plus, am folosit acest exemplu ca pretext pentru a arăta cum se capturează evenimentul de apăsare a
unei taste anume.

În sfârșit, putem desena Sliderul și vertical dacă:

 punem axis:’vertical’ in opțiunile suplimentare (inainte de range);

 redesenăm DIVurile astfel încât șina să fie înaltă și nu lată.

InPlaceEditor

Acest mecanism substituie un text din pagină cu un mic formular la apăsarea unui click pe text, apoi
stochează valoarea din formular înapoi în corpul paginii.

Pentru a realiza mecanismul de substituire nu e necesar Scriptaculous și AJAX, se poate realiza cu o


simplă înlocuire de conținut (cu innerHTML sau, dacă folosim Prototype, cu Element.update). Însă o
astfel de implementare ar avea dezavantajul:

 conținutul astfel inserat va fi vizibil până la primul Refresh de pagină;

 putem să-l facem persistent memorându-l într-un cookie (cum am făcut la


Autocompleter.Local), dar tot rămâne dezavantajul că singurul utilizator care va putea vedea
acel conținut este chiar cel care l-a scris; fiecare utilizator are acces doar la cookieul propriu, deci
fiecare va vedea ce a scris el, dar nu și alți.

Ori scopul principal al lui InPlaceEditor e să permită inserarea de conținut permanent și pentru toți
utilizatorii în pagină, de exemplu comentariul pe un blog sau pe un site. Asta înseamnă că obligatoriu
trebuie implicat serverul.

<!DOCTYPE html>
<html>

<head>

<script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>

<script src="scriptaculous/src/scriptaculous.js" type="text/javascript"></script>

<script>

function initializare()
Doar IDul şi scriptul
{ server sunt obligatorii.
Acoladele conţin un
new Ajax.InPlaceEditor("edt","sursaeditor.php",{rows:10,cols:10})
obiect JSON cu opţiuni
} suplimentare (aici
dimensiunile casetei)
</script>
E preferabil ca toate
</head>
editoarele InPlace din pagină
</html> să fie iniţializate la încărcarea
paginii (desigur, ele pot fi
<body onload="initializare()"> iniţializate şi la primul click pe
comentariu).
<div>

Acesta e continutul unui articol de ziar.<br/>

</div>

<div style="border:2px solid red; width:100px" id="edt">

Tastati aici comentariul dumneavoastra

</div>

</body>

Scriptul server (sursaeditor.php):

<?php

print $_POST["value"];
?>

Obs: În mod convenţional, scriptul server trebuie să îşi ia textul tastat din variabila cu numele value,
trimisă de InPlaceEditor prin metoda POST.

Acest exemplu de script server este minimal – nu face decât să recepționeze conținutul de la client și să
îl întoarcă înapoi pentru a fi inserat în pagină.

În practică, acest script mai realizează:

 obligatoriu: stocarea comentariului în baza de date, pentru a fi afișat tot timpul și pentru toți
viitorii utilizatori;

 opțional: postprocesarea comentariului, prin aplicarea unor concatenări (formatări, conținut


suplimentar) la textul comentariului înainte ca acesta să fie returnat la client; postprocesarea nu
e obligatoriu să aibă loc pe server (decât dacă necesită informații suplimentare din baza de date
sau din sesiune), dacă e vorba doar de formatări și mesaje suplimentare poate fi gestionată
dinamic și în JavaScript, cu ajutorul funcțiilor handler pentru evenimentele InPlaceEditor.

Ca opțiuni suplimentare, avem posibilitatea să personalizăm textele care apar pe butoanele Ok, Cancel
sau în timpul schimbului de date cu serverul, dacă să fie butoane sau linkuri etc.:

new Ajax.InPlaceEditor("edt","sursaeditor.php", {rows:10,cols:10,cancelControl:"button",

okText:"salveaza",cancelText:"anuleaza",savingText:"asteapta"})

Avem și posibilitatea să personalizăm stilurile aplicate asupra formularului inserat și asupra DIVului în
perioada sa de așteptare (Saving…). În special al doilea este important căci ne permite să afișăm un
indicator de așteptare (spre deosebire de Autocompleter, nu îl are implementat ca opțiune dedicată).

Stilurile trebuie să aibă următoarele nume standard (sunt 2 CLASSuri pe care InPlaceEditor le generează
automat, prin convenție):
<style>

.inplaceeditor-saving {background:url('spinner.gif') no-repeat;width:100px;height:100px}

.inplaceeditor-form {background:green}

</style>

Primul corespunde perioadei de așteptare (îi alocăm un GIF animat), al doilea corespunde formularului
(evident, poate fi personalizat pentru fiecare componentă a formularului, cunoscând faptul că acesta
conține o casetă textbox sau textarea, și 2 butoane sau 2 linkuri). Mai departe, în definirea
mecanismului, avem grijă ca textul de așteptare Saving… să nu mai apară, să nu se suprapună cu
imaginea.

new Ajax.InPlaceEditor("edt","sursaeditor.php",{rows:10,cols:10,savingText:"" })

În plus, ca și la celelalte instrumente, putem să alocăm funcții câtorva evenimente care au loc în timpul
procesului:

function initializare()

new Ajax.InPlaceEditor("edt","sursaeditor.php",

{rows:10,cols:10,onEnterEditMode:f1,onLeaveEditMode:f2,ajaxOptions:{onSuccess:f3}})

function f1()

alert('Se intra in modul editare')

function f2()
{

alert('Se iese din modul editare')

function f3()

alert('Transfer realizat cu succes')

Obs:

 onEnterEditMode și onLeaveEditMode sunt evenimente de comutare a stării (trecerea de la text


la formular și invers) specifice lui InPlaceEditor;

 pentru a testa succesul și eșecul schimbului cu serverul, evenimentele customizate nu


funcționează datorită unui bug…

o …soluția este să folosim opțiunea suplimentară ajaxOptions, ce ne permite să


transferăm configurări spre mecanismul de bază Ajax.Request, inclusiv evenimentele
suportate de acesta - onSuccess, onFailure etc.

 și aici putem personaliza o funcție callback, dacă dorim să reprogramăm întregul mecanism.

Avem și posibilitatea de a realiza comutarea editorului pe alte căi decât prin click pe paragraful original:

- introduceți după comentariu butonul boldat mai jos:

<div style="border:2px solid red; width:100px" id="edt">

Tastati aici comentariul dumneavoastra

</div>

<input id="but" type="button" value="apasa aici pt a edita comentariul"/>


- reconfigurati editorul, indicând IDul butonului și faptul că doar acel buton poate controla comutarea
editorului:

new Ajax.InPlaceEditor("edt","sursaeditor.php",

{rows:10,cols:10,externalControl:"but",externalControlOnly:true})

Pentru a opri definitiv funcționalitatea, avem funcția dispose() ce se poate apela oriunde în pagină, dacă
dăm un nume obiectului InPlaceEditor:

ed=new Ajax.InPlaceEditor("edt","sursaeditor.php",{rows:10,cols:10 })

……apoi, undeva în pagină creăm un buton sau alt mecanism care să apeleze oprirea….

<input type="button" value="opreste editorul" onclick="ed.dispose()"/>

Sarcina de lucru:

Modificati mecanismul astfel incat pagina sa afiseze 2 articole de blog, fiecare cu cate 1 comentariu
sub el (luate dintr-o baza de date cu articole si comentarii).

 Toate comentariile sa fie modificabile cu InPlaceEditor care, pe server, substituie in baza


de date comentariul cu cel tastat.

 Sub comentarii sa mai fie cate un DIV pentru adaugare de comentariu nou (tot cu
InPlaceEditor, dar de data aceasta pe server va avea loc adaugare de comentariu in baza
de date, nu substituire!).

Sugestie privind baza de date:

Un tabel cu articole: IDarticol, Textarticol

Un tabel cu comentarii: IDcomentariu, IDarticol, Textcomentariu


(e acceptabil si daca realizati exemplul cu un tabel unic, redundant, cu toate cele 4 coloane ceea ce
face interogarile SQL mai simple; totusi, in proiecte reale, acestea se pastreaza in tabele separate
datorita relatiei 1 la n dintre fiecare articol si comentariile sale)

Sugestie privind licenta: cei care faceti siteuri Web la licenta, ganditi-va cum controlati faptul ca un
utilizator sa isi poata modifica doar PROPRIILE comentarii (idee: baza de date va trebui sa contina si
IDul autorului iar dupa logare si crearea sesiunii, in PHP se genereaza InPlaceEditoare doar pentru
comentariile ce au ca autor userul logat).

Lab 5

Interfața Oxygen și buna formare

Creați în Oxygen un document XML nou.

În fereastra de creare se pot observa jos două butoane:

 butonul Customize ne lasă să precizăm un vocabular, DACĂ dorim ca documentul nostru să se


alinieze (să se valideze) față de regulile impuse de un vocabular;
 deocamdată nu creăm vocabulare, așa că apăsăm Create.

Introduceți următorul document XML:

<?xml version="1.0" encoding="UTF-8"?>


<produs pret="100">Televizor</produs>
<produs pret="200">Ipod</PRODUS>
<pret total>300</pret total>

Imediat ce tastați, vi se atrage atenția că acesta nu e un document XML bine format. Acest mesaj apare
automat în partea de jos a documentului sau, dacă apăsați butonul de testare a bunei formări, în zona
de output.
Obs: Atunci când zona de output se încarcă prea tare cu mesaje, o puteți curăța. De asemenea, puteți
reveni oricând la mesajele precedente.

Un exemplu de subiect pentru proba practică (sau pt scris) ar fi să identificați care sunt regulile de bună
formare pe care le încalcă documentul?

În exemplul de față, răspunsul ar fi:

 documentul nu are rădăcină unică (are 3 elemente pe primul nivel);

 al doilea element nu se închide (</PRODUS> cu </produs> nu e totuna, deoarece XML e case


sensitive)1;

 al treilea element nu are voie să conțină spațiul, acesta fiind rezervat pentru separarea de
atribute; practic se consideră că PRET e marcatorul iar TOTAL e un atribut lipsit de valoare, deci
eronat (orice atribut trebuie să aibă o valoare între ghilimele).

În continuare creați următorul document bine format:

1
În text voi folosi majuscule de câte ori mă refer la marcatori și atributele lor, ca să fie mai ușor de citit textul.
<?xml version="1.0" encoding="UTF-8"?>
<comanda>
<!-- acesta e un comentariu -->
<produs denumire="Televizor" id="p1" pret="100"/>
<produs id="p2" pret="200">Calculator</produs>
<produs>
<id>p3</id>
<pret>300</pret>
<denumire>Ipod</denumire>
</produs>
<prettotal>600</prettotal>
Aceasta este o comanda de produse
</comanda>

După cum am discutat la curs, e un document cu o structură nerecomandată, neregulată, dar ne permite
să exersăm diverse aspecte.

În timpul tastării, observați că Oxygen are o serie de facilități de tastare prin care încearcă să vă
împiedice de la a crea elemente prost formate: de câte ori scrieți un marcator vă adaugă automat
închiderea lui, de câte ori modificați un marcator vă modifică și perechea lui, de câte ori deschideți
ghilimele vi le închide automat etc. În principiu trebuie să depui efort pentru a scrie prost un document
XML și acest efort va fi puternic penalizat la proba practică.

Dați un click pe primul produs. Veți remarca o serie de aspecte printre care: generarea automată a unei
căi Xpath absolute care ar putea returna elementul selectat; sublinierea cu gri a elementului selectat
este importantă pentru căile RELATIVE, care vor fi calculate de la nodul subliniat; în dreapta se mai
poate vedea și zona de gestiune a atributelor elementului selectat
Alt aspect de interfață important e modul de vizualizare a documentului:

 modul Text este ceea ce se vede în acest moment;

 modul Author este varianta formatată, cu condiția să se fi creat stiluri CSS pentru marcatori (nu
e cazul deocamdată, vom primi o eroare CSS dacă încercăm să intrăm pe Author);

 modul Grid este un alt mod de afișare a arborelui DOM, care e util pentru că ne arată exact câți
copii are fiecare nod (în timp ce în modul Text acest lucru nu e întotdeauna evident).

De exemplu, la întrebarea câți copii are nodul COMANDA?, ați putea fi tentați să răspundeți cu 6:

 1 comentariu, 3 elemente PRODUS, 1 element PRETTOTAL si 1 nod text.

În realitate numărul de copii este 11, pentru că mai există încă 5 Enteruri considerate noduri text
(însoțite uneori de spații sau Taburi pt indentare!):

<?xml version="1.0" encoding="UTF-8"?>


<comanda>
<!-- acesta e un comentariu -->
<produs denumire="Televizor" id="p1" pret="100"/>
<produs id="p2" pret="200">Calculator</produs>
<produs>
<id>p3</id>
<pret>300</pret>
<denumire>Ipod</denumire>
</produs>
<prettotal>600</prettotal>
Aceasta este o comanda de produse
</comanda>

De ce nu sunt considerate noduri text și celelalte 5 Enteruri?

 Primele trei sunt noduri text, dar sunt copiii celui de-al treilea PRODUS, nu al lui COMANDA;

 Ultimele două sunt ale lui COMANDA, dar fac parte din același nod text ca și propoziția pe care o
încadrează. Deci ultimul nod text este de fapt {ENTER}Aceasta este o comanda de
produse{ENTER}

Prezența tuturor Enterurilor va afecta rezultatele căilor Xpath bazate pe poziție. În programare se evită
în general prezența Enterurilor prin câteva metode:

 dacă documentul XML e luat dintr-un fișier sau string, avem grijă ca în acel fișier/string să scriem
totul pe un singur rând, fără Enteruri (dezavantaj: dificultate de citire și editare!)

 se încarcă fișierul/stringul cu Enteruri cu tot și se creează o funcție de curățare recursivă a


nodurilor invizibile (Enteruri, Taburi etc.);

 dacă se construiește documentul XML direct prin program, nod cu nod, această problemă nu
mai apare căci funcțiile de lipire a nodurilor la arborele DOM (appendChild, insertChild etc.)
generează un XML curat, fără noduri invizibile!

În Oxygen vom păstra Enterurile pentru citirea mai ușoară a exemplelor. Oricum, am amintit deja că
vizualizarea în mod Grid ne evidențiază mai bine aceste noduri invizibile.
Afișarea în Grid se face pe nivele, cu posibilitatea de a detalia sau restrânge conținutul fiecărui element,
folosind săgețile. Prntre elemente se pot vedea nodurile invizibile, cu numele și valoarea (rând gol).

La ultimul nod text se poate vedea că a fost reunit cu un Enter (rând gol) și un Tab în față, iar în spate cu
un Enter (comparativ, valoarea 600 de deasupra, nu are nici un rând gol sau spațiere).

Obs: în versiunea curentă Oxygen are un bug: dacă dați click pe ultimul PRODUS din Grid, nu veți vedea
nodurile invizibile deși are și acesta 3. O metodă mai sigură de a vedea corect arborele DOM complet
este opțiunea Tools- Tree Editor:
La vizualizarea cu Tree Editor, pentru a vedea toate nodurile, mai trebuie să activați butoanele care fac
vizibile atributele, nodurile invizibile (white spaces) și comentariile.

Se pot vedea nodurile invizibile (casetele goale cu un T în față).


Probleme legate de arborele DOM

În concluzie, arborele DOM al acestui exemplu arată astfel:

Documentul
(nodul document)

<?xml ...?> comanda


(nod de tip PI) (elementul document)

Nod comentariu produs produs produs Nod text prettotal Aceasta este o
Nod text Nod text Nod text Nod text comanda de produse
invizibil invizibil (nod element) produs invizibil (nod element) produs invizibil (nod element) produs invizibil(nod element) produs (nod text
vizibil+invizibil)

Id=”p1”
Id=”p2”
pret=”100” pret=”200”
denumire=”Televizor”

Nod
id Nod
pret Nod
denumire Nod
Calculator text(nod element) produstext(nod element) produstext(nod element) produstext 600
(nod text vizibil) invizibil invizibil invizibil invizibil (nod text vizibil)

p3 300 Ipod
(nod text vizibil) (nod text vizibil) (nod text vizibil)

Acest arbore are 5 nivele (atributele nu formează un nivel!)


Există câteva dileme cu care se confruntă începătorii (datorită unor confuzii legate de terminologie,
confuzii generate de literatura de specialitate):

Confuzia 1: Rădăcina

Termenul Rădăcină este folosit cu două sensuri:

 pentru a se referi la rădăcina reală a arborelui DOM (documentul în ansamblu, termenul


standard fiind the document node, nodul document);

 pentru a se referi la marcatorul/elementul rădăcină (COMANDA în cazul nostru, termenul


standard fiind the document element, elementul document).

Problema e că în nici unul din cele 2 cazuri termenul standard nu e root, așa că autorii folosesc în mod
liber cuvântul rădăcină pentru a se referi ba la unul, ba la celălalt. Eu în general folosesc termenul
rădăcină pentru a mă referi la marcatorul care conține toți ceilalți marcatori (elementul document),
considerând că toată lumea știe că acesta trebuie obligatoriu precedat de declarația <?xml...?>.

Confuzia vă poate afecta cel mai des atunci când creați un arbore DOM prin programare, nod cu nod.
Există tentația de a crea toate elementele, a le lipi între ele, dar a uita că și elementul rădăcină trebuie
lipit la document.

De exemplu, în PHP, crearea unui arbore DOM trebuie să înceapă cu linia:

$doc=new DOMDocument();

(crearea nodului document)

....și să se termine cu:

$doc->appendChild($radacina)

(lipirea rădăcinii la document, după ce în rădăcină s-a introdus tot ce trebuie)

Confuzia 2: Sunt atributele noduri?


Această confuzie e legată de semnificația atribuită termenilor nod și atribut, confuzie creată chiar de
standardul DOM, care se referă la atribute prin expresia "noduri de tip atribut".

Totuși, în general atributele sunt tratate diferit de noduri. De exemplu:

 căutările Xpath generice după funcția node() vor returna ORICE nod (de orice tip: text,
elemente, comentarii etc.) dar nu și atribute (căutarea generică de atribute se face cu @*);

 nodurile au o ordine relevantă (le accesăm de obicei pe bază de poziție) în timp ce atributele de
obicei se accesează pe bază de nume (ordinea lor e irelevantă – 2 marcatori care au aceleași
atribute în ordini diferite sunt considerați echivalenți); totuși, sunt și softuri care permit
parcurgerea atributelor pe bază de poziție (în ordinea în care au fost scrise) dar nu e bine să ne
bazăm pe acea ordine;

 atributele vor fi ignorate de funcțiile care caută pe baza relațiilor de rudenie (copil/frate/etc.);
chiar și în standardul DOM, atributele nu fac parte din vectorul childNodes, au propriul vector,
numit attributes.

Cel mai sigur este să vă gândiți la atribute ca la niște proprietăți ale nodurilor de tip element și nu ca la
noduri-copil ale acestora. Din acest motiv în desenarea arborelui DOM nu am desenat atributele pe
următorul nivel (4), ci între nivele (3 și 4) sugerând astfel faptul că nu sunt chiar noduri-copil, ci au un
statut aparte.

Confuzia 3: Numele și valoarea nodurilor

În sfârșit, o confuzie care afectează mai mult la nivel de programare este semnificația termenilor valoare
de nod/ nume de nod.

Conform standardului DOM, orice nod (și orice atribut) trebuie să aibă nume și valoare. În cazul
atributelor acest lucru e evident, dar la elemente și noduri text e mai puțin evident. De exemplu care e
numele și valoarea în cazul <produs>Televizor</produs> ???

Răspunsul intuitiv ar fi că “produs” este numele și “televizor” valoarea. În realitate, există două viziuni:
A. Standardul DOM specifică astfel:

În acest exemplu avem 2 noduri, unul de tip element, cu un fiu de tip text. FIECARE din acestea are un
nume și o valoare!

 Nodul element are numele “produs” și valoarea null!

 Nodul text are numele standard #text și valoarea “Televizor”!

Orice programator care lucrează cu standardul DOM va trebui să țină de acest fapt (stringul "Televizor"
nu e valoarea directă produsului, ci valoarea primului copil al nodului PRODUS). Pentru accesarea
stringului Televizor, prin funcții DOM va fi necesară o construcție de genul (PHP):

$x=..... (stocarea elementului PRODUS într-o variabilă prin getElementBy…, childNodes[] sau alte
metode)

$text=$x->firstChild->nodeValue (capturarea textului din marcator)

$tag=$x->nodeName (capturarea marcatorului)

B. Deoarece această viziune nu e tocmai intuitivă, multe limbaje (atât de programare, cât și XML
Schema, XSLT sau Xpath) ocolesc sau extind standardul DOM cu niște funcții care să trateze acest caz în
mod mai intuitiv: „produs” este numele, „Televizor” este valoarea.

În această viziune, programarea accesului devine mai simplă (PHP):

$x=..... (stocarea elementului PRODUS într-o variabilă)

$text=$x (variabila $x nu va mai conține un pointer spre nodul PRODUS, ci chiar conținutul textual al
acestuia!)

$marcator=$x->getName (capturarea marcatorului)

Comparați cele două sintaxe. Le vom exemplifica într-un seminar viitor, când vom construi un XML prin
programare. Varianta A ar trebui să funcționeze în orice limbaj (fiind standardizată), varianta B nu
neapărat (dar fiind foarte populară, e disponibilă sub forma a diverse biblioteci pentru majoritatea
limbajelor).
În programare, trebuie să fiți atenți care din variante este suportată de limbajul cu care lucrați. Multe
limbaje le suportă pe AMBELE:

 În PHP, dacă am creat documentul cu clasa DOMDocument, trebuie să respectăm standardul


(varianta A), dacă îl creăm cu clasa SimpleXMLElement, putem folosi varianta B!

 În JavaScript există suport universal pentru standardul DOM (dar diferă de la browser la browser
numele clasei); pentru a putea folosi varianta B există extensia E4X, dar nu e recomandată căci
Internet Explorer refuză să o implementeze (poate fi testată în Firefox dar și acolo are unele
buguri).

Ca mod de gândire, Xpath e mai aproape de versiunea B (consideră că valoarea unui element e chiar
conținutul său).

Limbajul XPath

Reminder: Xpath este limbajul fundamental de interogare a documentelor XML, indiferent de scopul lor
(fie că sunt documente propriu-zise, programe scrise în limbaje cu taguri sau structuri de date de
transferat, termenul oficial este tot documente XML).

Pe Xpath se bazează majoritatea limbajelor care manipulează cod XML:

 XSLT și Xquery pentru transformare de XML,

 XML Schema pentru crearea de vocabulare,

 Xpointer – o variantă mai complexă a lui Xpath care, în plus față de acesta, poate accesa și
altceva decât noduri din arborele DOM, de exemplu locații (poziții din documente), fragmente
(orice porțiune dintre două poziții, de exempu bucăți de taguri, bucăți de atribute) sau colecții
de fragmente;
 XML Signature și XML Encryption – pentru semnarea digitală sau criptarea unor noduri din
document.

Ca și SQL, Xpath suportă și anumite calcule aplicate asupra rezultatelor, iar de la versiunea Xpath 2.0,
suportă inclusiv:

 interogări condiționale if A then B else C, unde A,B,C sunt interogări Xpath (dacă interogarea A
găsește soluții, atunci execută interogarea B, altfel pe C),

 iterative: for $i in X return Y, unde X, Y sunt interogări Xpath ,iar X e o interogare care a
returnat mai multe rezultate (pentru fiecare rezultat al interogării X, execută interogarea Y);

 operații pe mulțimi (intersecție, reuniune, diferență între rezultatele unor interogări ce


returnează mai multe soluții);

 expresii regulate.

Xpath 2.0 e încă slab implementat. În JavaScript și PHP deocamdată se pot rula doar interogări Xpath
1.0.

Spre deosebire de SQL, Xpath poate doar citi informații, nu și modifica/insera/șterge. Pentru acestea se
apelează la funcțiile standard DOM (dacă modificările se fac direct pe documentul inițial) sau la limbajele
de transformare XSLT/Xquery (dacă dorim să generăm un nou document în urma modificărilor).
Interogările Xpath se execută în caseta dedicată din stânga sus, unde se poate selecta și versiunea Xpath
în care dorim să lucrăm.

Obs: Xpath 2.0 SA reprezintă interogări Schema Aware, pentru documente cu vocabular – de exemplu
dacă prin interogare căutăm un element care nu are cum să existe în document (din cauză că este
interzis de vocabular), atunci nu are sens să se mai proceseze interogarea. Interogările de tip SA sunt cele
care verifică dacă are sau nu sens interogarea, în funcție de regulile impuse de vocabularul
documentului.

În timpul tastării unei inteorgări, apar liste de sugestii și un Help contextual, foarte util în familiarizarea
cu cuvintele cheie din XPath

Rezultatele se afișează în panoul de output:

În mod implicit, interogările sunt căi absolute, încep de la rădăcină. Dacă dorim căi relative, trebuie să
alegem nodul curent, printr-un click pe marcatorul dorit, care va fi subliniat cu gri.
Executarea unei interogări nu dă rezultatul ca un simplu string, ci indică și poziția din document la care s-
a găsit rezultatul:

/comanda/produs/id

....caută elementele ID, din PRODUS, din COMANDA și afișează rezultatul:

/comanda[1]/produs[3]/id[1] – p3

....indică faptul că rezultatul se află în primul COMANDA, al treilea PRODUS, primul ID și are valoarea
“p3”.

Căi absolute simple (fără axe, fără condiții)

/comanda/produs/@id

...caută atributul ID al elementelor PRODUS din COMANDA (se vor găsit 2 soluții, cu valorile „p1” și “p2”)

/comanda/produs

....toate elementele PRODUS din COMANDA (3 soluții)

/comanda

...toate elementele COMANDA (returnează elementul rădăcină)

... returnează nodul document

Căi generice

/comanda/*
...toate elementele din COMANDA (4 soluții)

/comanda/text()

....toate nodurile text din COMANDA (6 soluții, din care 5 sunt nodurile invizibile)

/comanda/node()

....toate nodurile din COMANDA, indiferent de tip (11 soluții din care 6 text, 4 element, 1 comentariu)

/comanda/comment()

....toate comentariile din COMANDA (1 soluție)

/comanda/produs/@*

....toate atributele din toate elementele PRODUS din COMANDA (5 soluții, 3 la primul PRODUS, 2 la al
doilea)

/comanda/produs/node()

...toate nodurile din toate elementele PRODUS din COMANDA (se observă că nu s-au returnat atributele!
8 soluții, din care la al doilea PRODUS un text, iar la al treilea 3 elemente și 4 noduri invizibile)

Dacă dorim și nodurile și atributele scriem două interogări separate prin bară verticală:

/comanda/produs/node()|/comanda/produs/@*

În acest fel, Xpath 1.0 permite reunirea soluțiilor din mai multe căi. Xpath 2.0 oferă și tehnici mai
avansate (intersecție, diferență etc.)

Căi cu rezultat boolean


Acestea nu returnează noduri sau valori din document, ci verifică dacă există sau nu anumite noduri sau
valori:

/comanda/produs/id='p3'

...există nodul ID cu conținutul (valoarea) p3 în vreunul din elementele PRODUS din COMANDA? (true)

/comanda/produs/pret>100

....există vreun PRET cu valoare peste 100 în vren PRODUS din COMANDA? (true)

/comanda/*=600

...există vreun element cu valoarea 600 în COMANDA? (true, e PRETTOTAL)

/comanda/*/pret=300

...are COMANDA un nepot PRET cu valoarea 300? (true)

/comanda/*/@*="Televizor"

....există vreun atribut cu valoarea “Televizor” în oricare din fiii lui COMANDA? (true)

/*/*="Calculator"

...are elementul rădăcină un fiu al cărui conținut e stringul “Calculator”? (true)

/*/*/*="Ipod"

...are elementul rădăcină un nepot cu conținutul “Ipod”? (true)

Ca regulă generală, interogările booleene se termină cu o expresie logică care NU apare între paranteze
pătrate (dacă apare, înseamnă că e o restricție aplicată nodului precedent, vezi mai jos).
Căi cu salt

Când nu suntem siguri pe care ramură și pe ce nivel se găsește rezultatul se pot realiza căutări cu salturi.
În general nu sunt recomandate datorită performanțelor slabe pe arbori DOM foarte mari (trebuie
parcurse toate ramurile descendente!). Saltul este indicat prin //

//@*=100

...există oriunde în document un atribut cu valoarea 100? (true)

//*="Ipod"

...există oriunde în document un element cu valoarea Ipod? (true)

/comanda//@id="p1"

...există oriunde în COMANDA un atribut ID cu valoarea p1? (true)

/comanda//@id

...returnează toate atributele ID din COMANDA, indiferent de nivelul pe care se află (2 soluții)

//@id|//id

...returnează toate IDurile din document, indiferent că sunt atribute sau elemente, indiferent cui aparțin
(3 soluții)

Căi cu restricție (condiție)

Restricția se pune între [] și poate fi aplicată oricărui nod din cale!

Cele mai simple sunt restricțiile de poziție:


/comanda/produs[1]

...returnează primul PRODUS din COMANDA (atenție, în Xpath-ul din Oxygen numerotarea începe de la
1, dar în unele browsere poate începe de la zero!)

/comanda/produs[last()]

...returnează ultimul PRODUS din COMANDA

//@*[1]

...returnează toate atributele ce apar primele în elementele de care aparțin (2 soluții)

//*[last()]

...returnează toate elementele ce apar ultimele în cadrul nodului de care aparțin (3 soluții, inclusiv
rădăcina, care e ultima din document!)

Sunt frecvente și retricțiile privind conținutul:

/comanda/produs[id]

...returnează acele PRODUSE din COMANDA care conțin elemente ID (1 soluție)

/comanda/produs[@id]

...returnează acele PRODUSE din COMANDA care conțin atribute ID (2 soluții)

/comanda/produs[text()]

...returnează acele PRODUSE din COMANDA care conțin noduri text (2 soluții)
//*[text()="Calculator"]

...returnează toate elementele care conțin nodul text “Calculator” (1 soluție)

Restricțiile permit folosirea lui or, not, and pentru condiții mai complexe:

/comanda/produs[@id or id]

....returnează acele PRODUSE din COMANDA care au ID fie ca atribut, fie ca element (3 soluții; ne
scutește de a scrie două căi separate prin |)

/comanda/produs[not(@id)]

...returnează acele PRODUSE din COMANDA care nu au atribut ID (1 soluție)

//produs[not(node())]

...returnează acel PRODUS care este vid (1 soluție)

//produs[not(@*)]

...returnează acel PRODUS care nu are atribute (1 soluție)

/comanda/produs[position()=1 and @id]

...returnează acel PRODUS care ocupă prima poziție (între frați) și are atribut ID (1 soluție)

(când combinăm restricția de poziție cu altele nu putem folosi produs[1 and @id], deoarece în expresii
booleene orice număr diferit de zero e convertit în true, de aceea apelăm la funcția position)

Restricțiile nu se aplică doar pe ultima treaptă din cale. Putem să le aplicăm și undeva la mijlocul căii:

/comanda/produs[@id]/text()
...returnează nodurile text din acele PRODUSE cu atribut ID, din COMANDA (1 soluție)

Căi cu axe

Axele sunt direcțiile pe care înaintează calea. În mod implicit, o cale merge în jos. În mod explicit putem
schimba direcția căii cu:

 @ pentru a merge pe axa atributelor;

 // pentru a căuta în nodul curent și toții descendenții săi;

 .. pentru a merge în sus, la părinte;

 . pentru a face referire la nodul curent (folosit pentru a pune condiții asupra nodului curent).

În general .. se folosește în căile relative, ca și la foldere, pentru a urca un nivel în sus.

La foldere nu are sens să o luăm în sus în căi absolute: C:\folder1\folder2\.. e echivalent cu C:\folder1!
Aici însă are sens, căci e posibil să ajungem la un nod și pe altă cale decât coborând prin părintele său!
(prin salt cu // sau o traversare orizontală peste frați)

//text()/..

...returnează părinții tuturor nodurilor text (7 soluții)

//id/ancestor::node()

...returnează toți strămoșii elementelor ID (3 soluții)

//text()[.=”Calculator”]

...returnează toate nodurile text egale cu “Calculator” (1 soluție)

Vedeți cum s-a pus condiția asupra nodului curent cu ajutorul punctului!
 Dacă am fi scris //text()=”Calculator” am fi obținut o interogare booleană – există noduri text
egale cu “Calculator”??

 Dacă am fi scris //node()[“Calculator”] am fi obținut TOATE nodurile, deoarece expresia din


paranteze trebuie să fie true sau false, iar prezența unui string oarecare e echivalată cu true!

 Am fi mai aproape de rezultat cu o construcție de genul //node()[text()="Calculator"], ce dă


toate nodurile care conțin un nod text egal cu “Calculator” (în timp ce punctul ne permite să
returnăm chiar nodurile text ce sunt egale cu “Calculator”)

În plus mai sunt utile căile care traversează arborele pe orizontală:

/comanda/produs[3]/following::node()

...returnează toate nodurile care urmează în document după al 3-lea PRODUS din COMANDA (4, nu doar
frații!)

/comanda/produs[3]/following-sibling::node()

...același lucru, dar se returnează numai frații ce urmează (3 soluții)

La fel avem și axa nodurilor precedente:

/comanda/produs[3]/preceding::text()

...returnează toate nodurile text ce preced al 3-lea PRODUS din COMANDA (5, din care 4 noduri
invizibile)

/comanda/produs[3]/preceding::*/@*

...returnează toate atributele nodurilor precedente PRODUSULUI al 3-lea din COMANDA (5 soluții)

/comanda/produs[@id=”p2”]/preceding::node()[preceding-sibling::comment()]/..
..returnează părintele acelui element care e precedat de un frate de tip comentariu și care precede un
PRODUS cu ID=”p2”, din COMANDA

...altfel spus:

 se coboară în COMANDA

 se caută elementul PRODUS cu atribut ID=”p2”, cu produs[@id=”p2”]

 se caută predecesorii săi de orice tip (2 la număr, un PRODUS și un comentariu), cu


preceding::node()

 dintre aceștia se alege doar acel predecesor care are un frate precedent de tip comentariu, cu
[preceding-sibling::comment()]

 pentru acesta se caută părintele, cu .. (e chiar rădăcina)

Acesta e un exemplu complex care demonstrează detalii pe care începătorii le ignoră:

 Putem varia axa la fiecare pas al căii;

 Putem folosi axe în restricțiile dintre [];

 Putem folosi restricții la oricare pas al căii, nu doar la selecția rezultatului de pe ultimul pas.

Dacă o cale Xpath devine prea lungă, Oxygen vă deschide un panou suplimentar pentru continuarea
tastării.

Căi cu restricții bazate pe calcule/funcții

Funcția normalize-space elimină caracterele albe (spațiu, Enter, Tab) de la începutul și sfârșitul unui
string, iar în interiorul stringului păstrează maxim un spațiu între cuvinte.

//text()[normalize-space(.)=""]

...returnează toate nodurile invizibile din document (detectate prin faptul că, după ce li se aplică
eliminarea de caractere albe, nu mai rămâne nimic din ele; 9 soluții)
//text()[normalize-space(.)!=""]

...returnează toate nodurile text care nu sunt invizibile (6 soluții)

//text()[contains(.,"3")]

...returnează toate nodurile text care conțin caracterul 3 (2 soluții, observați din nou punctul folosit
pentru a pune o condiție asupra nodului curent)

//node()[contains(.,"3")]

...returnează toate nodurile indiferent de tip care conțin caracterul "3" (6 soluții! Pe lângă Idul și Pretul
returnate și în cazul precedent, aici apar și toate nodurile care conțin acel ID și acel PRET, deci inclusiv
PRODUSUL al 3-lea și elementul rădăcină: de reținut – contains caută în toți descendenții, nu doar în
conținutul textual direct)

//node()[count(*)=3]

...returnează toate nodurile care au exact 3 elemente fiu (1 soluție)

//node()[count(node())=1]

...returnează toate nodurile care au exact 1 nod fiu (5 soluții)

Mai multe funcții se prezintă în secțiunea următoare (toate pot fi folosite atât ca restricții cât și pentru
un calcul aplicat rezultatului final).

Căi cu rezultat bazat pe calcule/funcții

În această categorie intră căile asupra căror rezultate se aplică o funcție, de obicei una de agregare (sum,
count etc.).

count(/comanda/node())
... returnează numărul de noduri fiu ale rădăcinii (numărul 11, nu 11 soluții!)

contains(/comanda/produs[3],"3")

... returnează true, căci al 3lea PRODUS din COMANDA conține caracterul 3

sum(//produs/@pret)

... returnează 300, suma atributelor PRET găsite în PRODUSE

sum(//@pret | //pret )

...returnează 600, suma tuturor atributelor și elementelor PRET

concat(//produs[2]/@id,//produs[2])

...returnează "p2Calculator", concatenarea rezultatelor celor două căi (Idul plus conținutul celui de-al
doilea PRODUS)

name(//*[@id='p2'])

…returnează "produs", numele elementului care are atributul id egal cu 'p2'

name(/comanda/produs[1]/@*[2])

…returnează "id", numele atributului al doilea al primului PRODUS din COMANDA

Important: funcția name e mecanismul prin care putem afla numele unui element sau atribut la care am
ajuns pe alte căi decât cu ajutorul numelui (poziție, relație cu alte noduri etc.)

string(/comanda)

... convertește rădăcina în string (rezultatul fiind toate conținuturile textuale concatenate între ele)
substring(//produs[@pret=200],2,3)

...returnează "alc", subșirul decupat de la poziția 2, de lungime 3, din valoarea PRODUSULUI cu


PRET=200

substring-before(/comanda/text()[normalize-space(.)!=""], "com")

...returnează "Aceasta este o ", adică subșirul decupat de la început până la apariția lui "com", din acel
nod text care nu este invizibil (există și varianta substring-after)

string-length(/comanda/produs[2])

...returnează 10, numărul de caractere din al doilea PRODUS

translate(//prettotal,"0","9")

...returnează valoarea lui PRETTOAL, în care substituie toate zerourile cu 9

translate(//produs[count(node())=1],"acr","xy")

...returnează "Cxlyulxto", obținut prin substituirea lui a cu x, a lui c u y și a lui r cu nimic (datorită faptului
că al 3lea argument e mai scurt ca al doilea); modificarea se aplică aspra valorii acelui PRODUS care are
exact 1 nod fiu (deci al 2lea)

boolean(/comanda/produs)

...există vreun PRODUS în COMANDA? (true, funcția boolean e folosită pentru a converti orice tip de
interogare într-un booleană, dacă ne interesează doar existența unei soluții, nu și soluția în sine)

boolean(/comanda/*[position()=1 and self::produs])

...este PRODUS numele primului element fiu din COMANDA? (true, funcția boolean se combină cu o
declarație de tip self ce exprimă numele nodului curent, pentru a-i testa numele)
Important: funcția position nu poate fi folosită pentru a calcula poziția unui nod!

Deci nu putem calcula position(/comanda/produs[id]) pentru a obține poziția între frați a PRODUSULUI
ce are un fiu ID!

Vom apela la un artificiu, obținând poziția prin numărarea fraților precedenți:

count(/comanda/produs[id]/preceding-sibling::node())

...returnează 7 (numără toți frații precedenți, indiferent de tip)

Important: XPath 1.0 este foarte limitat în ce privește funcțiile de agregare – are doar sum și count. Alte
operații (medie, produs etc.) se pot realiza fie cu XPath 2.0 (ce permite structuri FOR și IF chiar în căi), fie
se pasează responsabilitatea spre limbajul de programare ce solicită interogarea.

Căi relative

Căile relative se calculează relativ la un nod curent. În Oxygen putem seta nodul curent printr-un click pe
un marcator (care primește subliniere gri). Dați un click pe al 3lea PRODUS.

Căile relative nu mai încep cu slash:

following::*

...returnează elementul următor (PRETTOTAL)

...returnează toate elementele fii ai nodului curent (3 soluții)

../*[1]

...returnează primul element fiu al părintelui nodului curent (primul PRODUS)


../node()[1]

...returnează primul nod fiu al părintelui nodului curent (primul nod invizibil)

count(*)

...returnează numărul de elemente fii ai nodului curent (3)

string-length(*[last()])

...returnează lungimea conținutului ultimului element fiu al nodului curent (4)

preceding-sibling::node()

...returnează precedenții frați ai nodului curent (7 soluții, inclusiv noduri invizibile și comentariul)

count(preceding-sibling::*[not(@*)])

...câți din frații precedenți nu au atribute (0)

preceding-sibling::*/@pret=100

...există între frații precedenți vreunul cu atributul PRET=100? (true)

id="p3"

...are nodul curent un fiu id cu conținutul "p3"? (true)

boolean(self::produs)

...are nodul curent numele produs? (true)


count(preceding-sibling::node())

...care e poziția, între frați, a nodului curent? (7) – deoarece nu putem folosi funcția position(), se
apelează la acest truc de numărare a fraților precedenți

Lab 6

XML Schema

XML Schema este cel mai popular limbaj pentru crearea de vocabulare XML. Reamintim că un vocabular
XML este un set de reguli (suplimentar celor de bună formare) ce limitează marcatorii, atributele și
modul lor de utilizare, iar documentele construite după un vocabular pot fi validate pentru a verifica
dacă au respectat regulile.

Există mai multe limbaje de creare a vocabularelor, dintre care DTD face parte chiar din standardul XML,
însă XML Schema a căpătat întâietate datorită unor avantaje precum:

 permite limitarea tipurilor și intervalelor de date permise într-un document;

 permite validare parțială/multiplă (când un document are marcatori din mai multe vocabulare);

 permite constrângere prin expresii regulate;

 permite definire unor restricții de tip "cheie primară/cheie străină" (deci și relații între
documente) mai flexibile decât DTD;

 este el însuși un limbaj XML, deci poate fi procesat ca arbore DOM, la fel ca orice document XML
(în timp ce DTD nu e de tip XML, necesită alt fel de interpretor).

În continuare vom crea un vocabular cu următoarele reguli:

 elementul rădăcină să fie Comanda;

o Comanda să conțină obligatoriu maxim 3 elemente Produs (deci ocurența


min.1,max.3) urmate de 1 element opțional Onorata (min.0,max.1):

 Produs să conțină 2 atribute obligatorii (CodProdus, Pret) și un nod text


obligatoriu (denumirea produsului);
 CodProdus să aibă valoare string unică ("cheie primară la nivel de
document") de exact 5 caractere;

 Pret să aibă valoare numerică pozitivă întreagă;

 Nodul text să accepte doar una din valorile Televizor, Calculator, Ipod;

 Onorata să conțină un nod text obligatoriu, cu o valoare booleană.

Filozofia Tipurilor

Filozofia XML Schema se bazează pe noțiunea de TIP, care e mai largă decât la alte limbaje. Un tip poate
fi:

 TIP PREDEFINIT: orice tip de date clasic (string, integer, plus câteva specifice XMLului precum
tipul ID – stringuri irepetabile, sau tipul Name – stringuri fără spații, sau tipul token – stringuri cu
maxim un spațiu între cuvinte) ;

 TIP SIMPLU: o submulțime a unui TIP PREDEFINIT (o listă de stringuri, un interval de valori
numerice etc.) sau o reuniune de submulțimi;

 TIP COMPLEX: un model complex obținut prin îmbinarea de marcatori, noduri text și atribute, ce
se dorește a fi reutilizat (o structură internă reutilizabilă a unui nod element).

În general e o idee bună ca pentru fiecare marcator și atribut prevăzut a apărea prin documente:

 să se definească un TIP cu toate regulile privind acel marcator/atribut;

 apoi să se declare apartenența marcatorilor/atributelor la TIPURILE definite.

În felul acesta, regulile capătă oarecare modularitate – se pot edita regulile la nivelul unui TIP, fără a
afecta marcatorii care nu au legătură cu acesta.

Obs: În general se evită crearea unui TIP pentru elementul rădăcină, având în vedere unicitatea lui,nu se
prea pune problema reutilizării sale. Sunt totuși situații în care și structura rădăcinii se poate reutiliza
ducând la recursivitate, când copiii rădăcinii moștenesc structura rădăcinii, nepoții la fel, etc. ramificând
arborele DOM în maniera fractalilor, teoretic la infinit. Practic nu se merge la infinit dacă structurii
recursive i se alocă un caracter opțional (astfel încât nodurile de pe ultimul nivel să poată să nu mai
conțină nimic).
Planificarea tipurilor

Tipurile se definesc într-o manieră bottom-up, pe baza cerințelor, pornid de la nodurile simple ale
viitoarelor documente (atributele și nodurile text) apoi urcând în arbore, prin noduri tot mai complexe.

În cazul de față:

1. Tipuri pt atribute:

 vom crea un TIP SIMPLU pentru atributul CodProdus:

o pornind de la TIPUL PREDEFINIT ID vom aplica o restricție de lungime – exact 5


caractere;

 pentru atributul Pret nu are sens să creăm un tip, există TIP PREDEFINIT pentru numere pozitive

2. Tipuri pt nodurile simple (care conțin doar text):

 vom crea un TIP SIMPLU pentru denumirile produselor:

o pornind de la TIPUL PREDEFINIT string vom aplica o filtrare de valori permise:


Televizor, Calculator, Ipod.

 pentru elementul Onorat nu are sens să creăm un tip, există TIP PREDEFINIT pentru valori
booleene.

3. Tipuri pt noduri complexe (care conțin și altceva decât text):

 vom crea un TIP COMPLEX pentru Produs:

o tipul va fi o structură alcătuită din atributele CodProdus, Pret și denumirea produsului


ca nod text (pentru toate 3 vom avea deja tipuri definite de la punctele precedente)

 vom evita să creăm un TIP pentru Comanda, căci nu dorim recursivitatea structurii rădăcinii.

Ca regulă generală:

 TIPURILE SIMPLE se creează pentru atribute sau elemente ce vor conține doar text;
 TIPURILE COMPLEXE se creează pentru elemente care pot avea și altceva în afară de un conținut
text (atribute sau elemente copil).

Crearea vocabularului

Creăm un fișier de tip XML Schema. Spre deosebire de documentele XML, vocabularele în Oxygen pot fi
create cu un foarte comod mod Design care ne permite să construim regulile în mod grafic și să generăm
automat codul sursă XML Schema.

În modul Design, cea mai mare parte din muncă se realizează în meniul click dreapta, cu primele două
opțiuni:

 New Global ne permite să construim TIPURILE;

 Edit Attributes ne permite să configurăm TIPURILE sau alte componente ale vocabularului
Avertismente:

 Edit Attributes NU se referă la atribute XML, ci la diverse proprietăți ale vocabularului (e o


coincidență de denumire care riscă să vă ducă în eroare);

 Panoul Attributes din dreapta oferă teoretic aceleași opțiuni cu Edit Attributes, dar Edit
Attributes adesea oferă și unele opțiuni în plus, așa că evitați să folosiți panoul.
Crearea tipurilor

Se începe cu TIPURILE SIMPLE, într-o abordare bottom-up, de la simplu la complex:

TIPUL SIMPLU pentru denumirile de produse:

 click dreapta – New Global – Simple Type

 dăm tipului un nume (ModelDenumiri)

Trebuie să-i spunem din ce TIP PREDEFINIT îl vom crea, și ce filtru vom aplica:

 click dreapta pe ModelDenumiri – Edit Attributes

 în jumătatea de sus a ferestrei alegem TIPUL PREDEFINIT: Base Type = xs:string

 în secțiunea Facets definim filtrul: Enumerations, apoi cu click dreapta – Add adăugăm pe rând
valorile permise
TIPUL SIMPLU pentru CodProdus:

 click dreapta (pe grupul SCHEMA) – New Global – Simple Type

 dăm tipului numele ModelCoduri

 click dreapta pe ModelCoduri – Edit Attributes

 TIPUL PREDEFINIT: BaseType=xs:ID

 filtru: length 5
TIPUL COMPLEX pentru Produs:

 click dreapta pe SCHEMA – New Global – Complex Type;

 dăm tipului un nume (ModelProduse)

Mai întâi ne ocupăm de conținutul textual al viitoarelor Produse:

 click dreapta pe ModelProduse – Edit Attributes;

 alegem TIPUL conținutului: BaseType = ModelDenumiri (iată cum integrăm TIPURILE SIMPLE
într-o structură de TIP COMPLEX); precizarea acestui tip va impune și obligativitatea denumirii
(fiind definită clar lista de valori, nu e permis șirul vid, deci absența conținutului!)

Apoi ne ocupăm de atribute:

 click dreapta pe ModelProduse – Append Child – Attribute

 dăm atributului un nume (CodProdus), apoi ne ocupăm de el:

o alegem TIPUL atributului: Type=ModCoduri (de unde va moșteni toate restricțiile)

o alegem obligativitatea: Use = required

 click dreapta pe ModelProduse – Append Child – Attribute

 dăm atributului nume: Pret:

o îi alegem TIPUL PREDEFINIT: Type=xs:positiveInteger

o alegem obligativitatea: Use=required

În acest moment toate TIPURILE sunt create:


Crearea vocabularului

Asta înseamnă crearea rădăcinii și structurii sale interne.

 Click dreapta pe SCHEMA – New Global – Element

 Îi dăm nume: Comanda

Structura internă va fi o SECVENȚĂ = șir de elemente într-o anumită ordine

 click dreapta pe Comanda – Append Child – Sequence

 click dreapta pe SECVENȚĂ – Append Child – Element

o îi dăm nume: Produs;

 click dreapta pe SECVENȚĂ – Append Child – Element

o îi dăm nume: Onorata

Le stabilim limitele de ocurență, caracterul opțional (min.ocur.=0) sau obligatoriu (min.ocur.=1), precum
și TIPUL conținutului:

 click dreapta pe Produs – Edit Attributes:


o TIP conținut: Type=ModelProduse

o Min.Occur. =1, Max.Occur.=3 (obligatoriu, maxim de 3 ori)

 click dreapta pe Onorata – Edit Attributes:

o TIP: Type=xs:boolean

o Min.Occur.=0, Max.Occur.=1 (opțional, maxim o dată)

Vocabularul final arată astfel:

Tipurile

Vocabularul propriu-zis

În modul Text puteți vizualiza codul sursă. Nu e foarte complicat, dar e mult de scris (se poate observa
pe codul sursă că limbajul XML Schema însuși e un vocabular XML).

Salvăm vocabularul cu numele vocabular.xsd

Validarea

Creăm un document XML nou. De data aceasta, la creare apăsăm Customize pentru a indica vocabularul,
în caseta Schema URL.

Încă de la creare, documentul va conține un element Produs, iar rădăcina va fi însoțită de o serie de
atribute care să facă legătura cu vocabularul (noNamespaceSchemaLocation).:
<?xml version="1.0" encoding="UTF-8"?>
<Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd">
<Produs CodProdus="" Pret=""></Produs>
</Comanda>

Prezența unui Produs vid e explicabilă prin caracterul său obligatoriu. Când un document e construit pe
baza unui vocabular, se pot genera automat o serie de noduri obligatorii sau valori implicite.

La apăsarea butonului de validare primim numeroase erori:

Putem testa pe rând regulile vocabularului, scriind un document corect, apoi încălcând regulile una câte
una, pentru a ne convinge de efectul acestora.

Următorul este un document valid:

<?xml version="1.0" encoding="UTF-8"?>


<Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd">
<Produs CodProdus="aaaaa" Pret="2">Televizor</Produs>
<Produs CodProdus="bbbbb" Pret="4">Ipod</Produs>
<Produs CodProdus="ccccc" Pret="10">Ipod</Produs>
</Comanda>

Deși lipsește elementul Onorata, nu e o problemă, acesta fiind definit cu caracter opțional. Nici faptul că
valoarea Ipod se repetă nu e o problemă, important e să nu se iasă din lista de valori permise. Tot valid e
și următorul document:
<?xml version="1.0" encoding="UTF-8"?>
<Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd">
<Produs CodProdus="aaaaa" Pret="2">Televizor</Produs>
<Onorata>true</Onorata>
</Comanda>

Posibil subiect de examen: să primiți un vocabular și un document și să găsiți invaliditățile.

Modificarea vocabularelor

Nu e obligatoriu ca la crearea unui vocabular să se lucreze cu TIPURI, așa cum am lucrat în acest mod. Se
pot impune toate restricțiile direct pe nodurile vocabularului, fără a mai crea tipuri.

Totuși, tipurile oferă avantaje legate de reutilizare și modularitate:

 mai multe atribute/elemente pot să aibă același tip, deci aceeași gamă de valori/structură
internă permisă (vezi exemplul următor);

 se pot crea tipuri din alte tipuri (s-a văzut deja cum ModelProduse include ModelDenumiri și
ModelCoduri);
 se pot aduce modificări la un tip fără a afecta restul vocabularului, care nu are legătură cu acel
tip (vezi mai jos).

De exemplu, în cazul de față dorim următoarele modificări:

 Codurile de produse să aibă structura: litera P, urmată de exact 3 cifre, urmate de cel puțin
încă un caracter oarecare.

 În loc de Onorata să poată să mai apară un grup de maxim 3 elemente ProdusIndisponibil, cu


aceeași structură internă ca și elementele Produs.

Pentru a modifica structura codurilor de produse e suficient să modificăm TIPUL ModelCoduri –


modificarea se va propaga peste tot în vocabular unde avem atribute (sau elemente!) de acest tip.

 Click dreapta pe ModelCoduri – Edit Attributes

 Renunțăm la restricția length=5 (din panoul Facets)

 În locul ei folosim restricția pattern = P[0-9]{3}.+

Iată o calitate foarte importantă a XML Schema – posibilitatea de a constrânge valori cu expresii
regulate!

Expresia regulată exprimă exact structura pe care o doream: să înceapă cu P, să urmeze un caracter din
intervalul [0-9] repetat de exact 3 ori, apoi un caracter oarecare repetat minim o dată (punctul e locțiitor
pt un caracter, + e indicator de repetiție cu obligativitate: minim o dată)

Pentru a preciza că elementul Onorata poate fi ALTERNAT cu un grup de elemente ProdusIndisponibil,


înlocuim Onorata cu un nod de tip CHOICE:

 Click dreapta pe Comanda – Append Child – Choice

 De nodul Choice se vor lipi toate alternativele între care oferim posibilitatea de a alege:

o cum Onorata va fi una din variante, o tragem cu mouseul până se lipește de nodul
Choice

o click dreapta pe nodul Choice – Append Child – Element și denumim noul element cu
numele ProdusIndisponibil.

Mai trebuie doar să precizăm că ProdusIndisponibil reutilizează aceeași structură ca și elementele


Produs, apoi să-i stabilim ocurențele:

 click dreapta pe ProdusIndisponibil – Edit Attributes

o Min.Occurs=1, Max. Occurs=3


o Type=ModelProduse

Forma finală a vocabularului va fi următoarea:

Un exemplu de document valid ar fi următorul:

<?xml version="1.0" encoding="UTF-8"?>


<Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd">
<Produs CodProdus="P999xx" Pret="200">Televizor</Produs>
<Produs CodProdus="P1111" Pret="50">Ipod</Produs>
<ProdusIndisponibil CodProdus="P123q" Pret="400">Calculator</ProdusIndisponibil>
</Comanda>

Dar documentul de mai jos?

<?xml version="1.0" encoding="UTF-8"?>


<Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd">
<Produs CodProdus="P999xx" Pret="200">Televizor</Produs>
</Comanda>
Și acesta e valid, chiar dacă lipsesc atât ProdusIndisponibil cât și Onorata! Asta din cauză că Choice ne
permite să alegem între 1-3 ProdusIndisponibil și 0-1 Onorata. Practic lipsa părții de după Choice
înseamnă că am optat pentru Onorata dar am profitat de caracterul opțional și nu l-am mai pus deloc.

XSLT

XSLT este limbajul de transformare a documentelor XML și se bazează pe Xpath pentru a extrage
informații dintr-un document sursă și a le pune într-un document rezultat (cu structură diferită). De
obicei XSLT e folosit pentru a genera pagini HTML din date XML, deci poate fi folosit și ca instrument
AJAX (conversia răspunsului de la server în cod HTML).

Pornim de la următorul document XML (salvat cu numele comanda.xml)

<?xml version="1.0" encoding="UTF-8"?>


<comanda>
<!-- acesta e un comentariu -->
<produs denumire="Televizor" id="p1" pret="100"/>
<produs id="p2" pret="200">Calculator</produs>
<produs>
<id>p3</id>
<pret>300</pret>
<denumire>Ipod</denumire>
</produs>
<prettotal>600</prettotal>
Aceasta este o comanda de produse
</comanda>

Creăm în Oxygen următoarea foaie XSLT (salvată cu numele transformare.xsl):

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">Text oarecare</xsl:template>
</xsl:stylesheet>

O foaie XSLT conține una sau mai multe reguli de substituire cu două componente:
match este calea Xpath a elementelor care vor
<xsl:template match="/"> suferi substituția
conținutul lui xsl:template este noul conținut
Text oarecare
care va intra în locul celui detectat de match.
</xsl:template>

Exemplul de față selectează întreg documentul (calea XPath "/" reprezintă documentul în ansamblul său)
și îl înlocuiește cu stringul "Text oarecare".

Pentru a executa transformarea, Oxygen impune să se creeze un scenariu de transformare (diverse


configurări).

Crearea scenariului e solicitată când încercăm să executăm transformarea.


Tipul scenariului va fi XSLT transformation dacă documentul activ din Oxygen e chiar transformarea
(dacă e XMLul original, alegem tipul XML transformation with XSLT).

Creăm un scenariu nou.

Setările esențiale sunt în prima și ultima categorie. Categoria FO Processor se folosește doar dacă facem
transformări de tip XSL-FO (o alternativă la CSS, dedicată formatării de cod XML).
Avem deja completată calea foii XSLT curente (codul currentFileURL e suficient). Trebuie să selectăm
manual adresa XMLului original.

În acest fel am precizat ce se va transforma (comanda.xml) și cum se va transforma (foaia curentă din
Oxygen). Mai trebuie să precizăm ce să se întâmple cu rezultatul transformării, în categoria Output:

Decidem să salvăm fișierul cu numele rezultat.xml și să deschidem imediat rezultatul în Oxygen.

Apoi apăsăm OK și Transform now. Rezultatul va fi:

<?xml version="1.0" encoding="utf-8"?>Text oarecare

Se observă că nu e obligatoriu ca XSLT:

 să returneze documente XML bine formate;


 să returneze bucăți din documentul original (poate să-l substituie complet cu orice, dar de
obicei rostul lui XSLT e să extragă anumite date de interes din sursă și să le reorganizeze într-o
structură nouă, de obicei o pagină HTML).

O concluzie importantă este că XSLT nu conservă implicit conținutul fișierului original! Dacă nu punem
nimic în regula de substituire, pur și simplu se returnează un document gol. Dacă vrem să conservăm
întocmai unele elemente din fișierul original, trebuie să definim explicit o regulă de conservare (=o
regulă care substituie acele elemente cu ele însele). Revenim mai târziu la regulile de conservare.

Obs: Dacă outputul transformării este un fișier XML, caracterele invizibile (spațiu, Tab, Enter) din
interiorul regulilor se conservă. Dacă outputul e HTML, acestea se ignoră. Deci pe un rezultat XML nu e
totuna dacă regula e:

<xsl:template match="/>Text oarecare</xsl:template>

sau

<xsl:template match="/>

Text oarecare

</xsl:template>

Reguli de substituire iterative

Substituirile iterative sunt printre cele mai populare în XSLT, pentru că permit să se parcurgă (cu un ciclu
FOR) toate rezultatele unei interogări XPath și să se aplice o transformare fiecăreia dintre acestea.
Adesea ele sunt îmbinate cu regulile de substituire alternativă (IF și CASE) care aplică substituții diferite
în funcție de o interogare XPath booleană.

În exemplul următor, pentru fiecare produs din COMANDA se va returna cuvântul Produs scris cu italic
(într-o pagină HTML):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="comanda/produs">

<i>Produs</i>

</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Obs:

 de data aceasta generăm cod HTML;

 am bifat și opțiunea de afișare în browser a rezultatului salvat.

Rezultat:

<?xml version="1.0" encoding="utf-8"?><i>Produs</i><i>Produs</i><i>Produs</i>


Modificăm exemplul pentru a afișa cuvintele unul sub celălalt și pentru a concatena o numerotare după
fiecare (Produs1, Produs2 etc.), adică echivalentul unui ciclu FOR de genul următor (dacă am fi în PHP):

for ($i=0;$i<3;$i++)

print "<i>Produs</i>".$i."<br/>";

O problemă spinoasă pentru începători e modul în care XSLT tratează conceptul de variabilă. De fapt
numele este impropriu – în XSLT 1.0 ele se comportă mai mult ca și constantele – li se poate atribui o
singură dată o valoare (dar valoarea poate să varieze, dacă li se atribuie o interogare Xpath ce dă mai
multe valori, deci nu sunt chiar constante!).

Aceasta face teoretic imposibil ciclul FOR de mai sus (care necesită ca $i să își schimbe valoarea de mai
multe ori).

Inițializarea unei variabile se realizează cu una din construcțiile:

<xsl:variable name="i" select="0"/>

(dacă valoarea este una simplă, echivalentul lui $i=0 în PHP, sau dacă valoarea e cod XML obținut printr-
o interogare XPAth, care se trece în select, în locul lui 0)

<xsl:variable name="i">

<i>Produs</i>

</xsl:variable>

(dacă valoarea este cod XML, care poate fi oricât de complex)

În funcție de conținutul variabilei, apelarea sa se face diferit:

<xsl:value-of select="$i"/>

(dacă valoarea e simplă sau dacă e cod XML din care vrem să luăm doar conținutul textual)

<xsl:copy-of select="$i"/>

(dacă valoarea e cod XML și vrem să-l luăm așa cum e, cu tot cu marcatori)
Problema este că nu putem inițializa de 2 ori aceeași variabilă. Rostul lor uzual este să asigure
reutilizabilitatea unei bucăți de cod XML. De exemplu precedenta transformare poate să aibă și
următoarea formă, având în vedere că nu facem decât să reutilizăm aceeași bucată de cod:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:variable name="i">
<i>Produs</i>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="comanda/produs">
<xsl:copy-of select="$i" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Începătorii sunt tentați să ignore faptul că variabilelor XSLT nu li se pot reatribui valori, și vor încerca să
implementeze un ciclu FOR care să afișeze Produs1, Produs2, Produs3 astfel:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:variable name="i" select="0"/>
<xsl:template match="/">
<xsl:for-each select="comanda/produs">
<i>Produs</i>
<xsl:variable name="i" select="$i+1"/>
<xsl:value-of select="$i"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Problema e că această linie nu se execută niciodată, deoarece variabila/constanta $i a fost deja


inițializată. Rezultatul va fi afișarea repetată a lui Produs1.
Totuși, cum putem ajunge la rezultatul dorit? Secretul stă în faptul că atributul select poate avea ca
valoare ORICE interogare Xpath (se vede cum e folosit la xsl:for-each). Tot ce trebuie să facem e să
pasăm responsabilitatea contorizării spre Xpath. Practic, nu dorim decât să facem o numărătoare a
produselor. Dacă vă mai amintiți din XPath, poziția unui element între frații săi se obține prin numărarea
fraților precedenți.

count(preceding-sibling::produs)

(calea e relativă la fiecare produs în parte, acestea fiind deja selectate de ciclul FOR).

Deci transformarea corectă este:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="comanda/produs">
<i>Produs</i>
<xsl:value-of select="count(preceding-sibling::produs)"/>
<br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

La executarea ei, se vede că apar cuvintele Produs1, Produs2, Produs3 unul sub altul (am concatenat și
un BR după număr).

Rețineți acest truc și faptul că sunt numeroase situațiile în care lipsa de flexibilitate a variabilelor din
XSLT poate fi compensată prin generarea unor valori variabile cu funcții Xpath.

În general linia
<xsl:value-of select=".........."/>
este una din cele mai des întâlnite din XSLT. Cu ajutorul ei se pot genera:

 atât valori calculate în XSLT (cu variabile);

 cât și valori extrase cu Xpath din documentul original.

Obs:Mai remarcați și faptul că adăugarea de conținut nou (HTML în acest caz) se face prin simpla scriere
de cod nou în interiorul lui xsl:template, fără să fie necesar un operator de concatenare explicit!

Reguli de substituire recursivă

Atunci când nici Xpath nu ne ajută în a depăși problema rigidității variabilelor, mai avem o soluție la
îndemână – regulile recursive.

O regulă recursivă e o regulă ce se apelează pe ea însăși, de obicei cu parametri. Mecanismul e similar cu


funcțiile recursive din alte limbaje: regula XSLT se comportă ca o funcție, parametrii ei se comportă ca
argumente ale unei funcții.

Problema cu numărătoarea produselor se poate rezolva și astfel:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/">
<xsl:call-template name="generare">
<xsl:with-param name="listaproduse" select="comanda/produs"/>
</xsl:call-template>
</xsl:template>

<xsl:template name="generare">
<xsl:param name="i" select="0"/>
<xsl:param name="listaproduse"/>
<xsl:if test="$listaproduse">
<i>Produs<xsl:value-of select="$i"/></i>
<xsl:call-template name="generare">
<xsl:with-param name="i" select="$i+1"/>
<xsl:with-param name="listaproduse" select="$listaproduse[position()>1]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Observați că e vorba de 2 reguli:

 prima are match="/" și are rolul de a aplica efectiv substituirea documentului;

 a doua nu are match, dar are name, deci poate fi apelată ca o funcție.

Un apel de "funcție" e format din două componente:

 xsl:call-template indică numele funcției apelate;

 xsl:with-param indică numele și valoarea argumentelor cu care se apelează funcția (în interiorul
funcției, acestea trebuie declarate cu xsl:param și cu aceleași nume ca și argumentele din apel).

De exemplu secvența (din prima regulă) de mai jos

<xsl:call-template name="generare">
<xsl:with-param name="listaproduse" select="comanda/produs"/>
</xsl:call-template>
s-ar traduce în programarea clasică prin construcții de forma (sintaxa e orientativă, nu aparține unui
limbaj anume):

listaproduse=<vectorul produselor din comanda>

generare(listaproduse)

în timp ce a doua regulă s-ar traduce prin:


function generare(listaproduse,i=0)

if (listaproduse)

write "Produs"+i

i=i+1

listaproduse=<vectorul listaproduse fara primul element>

generare(listaproduse,i)

Observați diferența între modul de lucru al variabilelor și modul de lucru al parametrilor. Parametrii își
pot schimba valoarea la apelul unei "funcții" deci nu sunt la fel de rigizi ca variabilele! De aceea putem
simula cicluri FOR prin funcții care se apelează pe ele însele cu un argument $i care tot crește la fiecare
apel.

Aici parametri/argumentele sunt:

 $i, inițializat cu zero la primul apel și incrementat apoi la fiecare apel ce urmează;

 $listaproduse, inițializată cu vectorul produselor la primul apel, și modificată prin eliminarea


treptată a câte unui produs, la apelurile următoare, până se golește (care e și condiția de
terminare).

Condiția de continuare a recursivității e <xsl:if test="$listaproduse">, adică oprim ciclul atunci când
vectorul listaproduse rămâne gol (s-au terminat de eliminat produsele din el).

Cum aminteam, recursivitatea se folosește adesea atunci când dorim să implementăm cicluri FOR în care
o variabilă își tot schimbă valoarea, iar această schimbare nu poate fi pasată spre Xpath (de exemplu
când calculăm produsul unor elemente, căci nu există funcția "product" în Xpath2).

2
XPath 1.0 are doar 2 funcții de agregare: sum și count.Restul operațiilor iterative (medie, produs, etc.) trebuie să
aibă loc în limbajul care găzduiește Xpath. Xpath 2.0 în schimb permite să se implementeze cicluri FOR chiar în căi,
făcând posibile astfel de operații iterative direct în căi Xpath. În general Xpath 2.0 și XSLT 2.0 simplifică mult
exemplele discutate aici, însă le-am putea testa doar în Oxygen, sunt încă slab suportate în mediile de programare
(iar în browsere deloc și nici nu s-au anunțat planuri de dezvoltare a browserelor în această direcție, tendința
acestora fiind să consolideze rolul lui JavaScript, nu să îl substituie cu XSLT!).
Reguli de substituire alternativă

Observați că până aici am generat un document nou fără să folosim conținutul existent în cel original (nu
chiar deloc, am folosit poziția produselor în ultimul exemplu). Rolul uzual al unei transformări XSLT este
să genereze cod și conținut nou concatenat cu cod și conținut din cel vechi. În următorul exemplu, în
locul cuvintelor Produs1, Produs2, Produs3, vom genera denumirile produselor.

Această sarcină e puțin complicată de faptul că documentul XML original are o structură neregulată –
primul produs are denumirea într-un atribut, al doilea îl are în conținutul textual, al treilea îl are într-un
element-fiu. Deci, chiar dacă parcurgem cu un ciclu FOR cele 3 produse, va trebui să implementăm o
structură CASE care să extragă denumirea de la caz la caz:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="comanda/produs">
<xsl:choose>
<xsl:when test="@denumire">
<xsl:value-of select="@denumire"/>
</xsl:when>
<xsl:when test="denumire">
<xsl:value-of select="denumire"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
<hr/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Structura CASE e definită cu xsl:choose. Aceasta conține un șir de variante definite cu xsl:when (fiecare
cu câte o condiție testată cu atributul test). Ultima variantă este xsl:otherwise (pentru cazul în care nici
una din ramuri nu a funcționat).

Ce face structura CASE?

 Dacă găsește un atribut denumire (test="@denumire") îi extrage valoarea (cu xsl:value-of);

 Dacă găsește un element denumire (test="denumire") îi extrage valoarea;

 Dacă nu găsește nici una din variante (otherwise), va considera că denumirea se află în
conținutul textual, deci valoarea elementului ("." reprezintă nodul curent în Xpath).

Observați că

 toate căile Xpath din atributele test și select sunt RELATIVE la nodul curent (produsul la care s-a
ajuns cu xsl:for-each);

 la rândul său, atributul select de la for-each e o cale RELATIVĂ la nodul selectat de match.

Rețineți că în general, XSLT folosește căi RELATIVE la nodul pe care s-a poziționat instrucțiunea-părinte
(cea cu match e părinte pt for-each, acesta e părinte pt choose și when etc.). Asta cu excepția
atributului match care folosește căi absolute (chiar dacă nu punem slash la începutul lor!)

Structura CASE nu e singura metodă prin care putem obține cele 3 denumiri! După cum am văzut la
exemplul cu numerotarea, XSLT conlucrează cu Xpath într-o manieră flexibilă – unele sarcini pot fi
transferate spre Xpath, inclusiv filtrarea/selecția de elemente.

Putem să transferăm responsabilitatea selecției și spre Xpath, cu același rezultat ca precedentul


exemplu:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:for-each select="comanda/produs">
<xsl:value-of

select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/>

<hr/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Observați cum am evitat structura CASE mutând decizia spre Xpath:

 self::node()[not(denumire) and not(@denumire)] returnează valoarea nodului curent cu


condiția ca acesta să nu aibă un fiu denumire și nici un atribut denumire;

 denumire returnează valoarea fiului denumire (dacă există, evident);

 @denumire returnează valoarea atributului denumire (dacă există).

Între cele 3 variante am pus operatorul Xpath | care returnează reuniunea soluțiilor celor 3 interogări.
Cum fiecare PRODUS e doar în una din cele 3 situații, se va returna valoarea dorită pt. fiecare caz!

O a treia variantă ar fi să evităm cu totul ciclul FOR (ceea ce nu e chiar recomandat, dar aici numărul de
produse e suficient de mic încât să le putem selecta individual).

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:value-of select="comanda/produs[1]/@denumire"/>
<hr/>
<xsl:value-of select="comanda/produs[2]"/>
<hr/>
<xsl:value-of select="comanda/produs[3]/denumire"/>
<hr/>
</xsl:template>
</xsl:stylesheet>
A patra variantă mută iarăși o parte din sarcini spre Xpath: de data asta exploatăm atributul match al
regulii – în loc să substituim tot documentul original, procedăm astfel:

 Îi substituim doar produsele (punând în match un Xpath care ne dă toate produsele);

 Nodurile care nu sunt produse le ștergem (cu o regulă generală de substituire cu șirul vid).

Primul aspect e rezolvat astfel (observați cum match nu mai selectează rădăcina, ci produsele):

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/comanda/produs">
<xsl:value-of

select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/>


<hr/>
</xsl:template>
</xsl:stylesheet>

Dacă rulați această transformare, veți observa:

 Produsele se substituie;

 Dintre restul nodurilor se conservă doar nodurile de tip text (inclusiv cele invizibile)! Aceasta e o
regulă implicită de funcționare în XSLT: orice nod care nu e afectat de nici o substituire e
eliminat, cu excepția nodurilor text.

Deci mai trebuie să punem o regulă care să elimine și aceste noduri text.

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/comanda/produs">
<xsl:value-of

select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/>


<hr/>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>

A doua regulă se aplică tuturor nodurilor de tip text și, neconținând nimic, le substituie pe toate cu șirul
vid, deci le șterge.

Obs1. În atributele match ni se permite să nu mai punem cele 2 slashuri, deci match="text()" e echivalent
cu match="//text()". Acest lucru e permis deoarece oricum valoarea lui match va fi considerată cale
absolută de căutare. E o mică abatere de la sintaxa XPath standard, dar e suportată de XSLT datorită
frecvenței cu care se fac match-uri prin căutare (fără să știm exact unde e elementul de care avem
nevoie).

Generarea unei pagini Web complete

Dacă percepem documentul XML ca o structură de date (sau chiar ca o bază de date), interogările XPAth
joacă rolul lui SQL, iar transformările XSLT joacă rolul unui generator de rapoarte. "Rapoartele" finale
sunt fie pagini Web (caz în care XSLT joacă rolul PHPului), fie bucăți de pagini Web (tabele, structuri de
div-uri) ce urmează să fie inserate într-o pagină mai mare cu metode Ajax.

Modificăm în continuare exemplul pentru a genera din codul XML o pagină HTML mai cuprinzătoare,
care să conțină:

 Un titlu;

 Un tabel cu 2 coloane: denumirile și prețurile;

 Dedesubt, o celulă mare (COLSPAN) cu suma prețurilor.

Am amintit la un moment dat că un document XML nu ar trebui să conțină noduri calculate (de ex.
totaluri) decât dacă e într-un stadiu final, gata de listare/afișare pentru userul final. Nodurile calculate de
obicei se generează la momentul producerii formei finale a documentului (ca la generarea de rapoarte
pentru baze de date). În cazul de față am putea prelua totalul direct din documentul original. Vom
presupune totuși că el nu există acolo și îl vom calcula în cadrul transformării.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<html>
<head>
<style>
#cap {color:red}
</style>
</head>
<body>
<h1>Lista produselor din comanda</h1>
<table border="1">
<tr id="cap"><td>Denumire</td><td>Pret</td></tr>
<xsl:for-each select="comanda/produs">
<tr>
<td>
<xsl:value-of

select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/>


</td>
<td>
<xsl:value-of select="pret|@pret"/>
</td>
</tr>
</xsl:for-each>
<tr>
<td colspan="2" align="right">
<xsl:value-of select="sum(//pret|//@pret)"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Observați că s-a generat o pagină Web completă, cu tot cu CSS (ar putea conține chiar și JavaScript!) Din
acest exemplu reiese posibilitatea de a folosi XSLT ca generator dinamic de pagini Web, asemănător cu
rolul pe care îl are PHP!
Regula de conservare

S-a văzut la început că XSLT nu conservă implicit codul XML original. Dacă dorim să se conserve anumite
elemente, trebuie să creăm o regulă de conservare care să substituie elementele cu ele însele:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>

Această regulă substituie elementul rădăcină cu el însuși, producând o copie a documentului original
(pentru execuție, modificați scenariul de transformare să nu se mai returneze html în browser).

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>

<xsl:template match="/">
<xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

Această regulă produce tot o copie a întregului document, dar elimină nodurile invizibile.

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

Această regulă conservă toate elementele produs. Pe lângă ele, datorită regulii implicite, se conservă și
nodurile text din afara produselor. Studiați diferența față de următorul exemplu:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy/>
</xsl:template>

</xsl:stylesheet>

În acest caz, am folosit xsl:copy în loc de xsl:copy-of. Diferența e că se conservă produsele dar FĂRĂ
conținut și atribute! Această tehnică se folosește când vrem să conservăm elemente existente dar să le
redefinim conținutul și atributele:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy>
<xsl:attribute name="NouAtribut">NouaValoare</xsl:attribute>

<NouFiu>Nou continut</NouFiu>

</xsl:copy>
</xsl:template>

</xsl:stylesheet>
Nu e obligatoriu să folosim xsl:copy pentru conservare. Putem pur și simplu să retastăm elementul
original, cu modificările dorite aplicate asupra lui:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<produs PretNou="{@pret|pret}">
<NouFiu>Nou continut</NouFiu>
</produs>
</xsl:template>

</xsl:stylesheet>

Observați cum am creat un atribut PretNou care își ia valoarea fie din atributul vechi pret, fie din
elementul pret (depinde pe care-l gasește).

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy>
<xsl:value-of select="@pret|pret"/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

În acest exemplu, conținutul produselor e redefinit să fie egal cu prețul (luat fie din atributul pret, fie din
elementul-fiu pret, de la caz la caz).
În următorul caz conservăm produsele, dar și conținutul lor textual.

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

</xsl:stylesheet>

Instrucțiunea xsl:apply-templates se utilizează de obicei împreună cu xsl:copy și are rolul ca, după ce s-a
făcut copia goală a elementului, să o umple cu rezultatul eventualelor reguli care mai există pentru fiii
elementului. În cazul de față, neexistând alte reguli, se aplică regula implicită de conservare a nodurilor
de tip text, deci obținem produsele fără atribute dar cu conținutul textual. Evident, avem și posibilitatea
de a defini reguli proprii care să fie invocate de xsl:apply-templates:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda/produs">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

<xsl:template match="produs/*|produs/text()[normalize-space(.)!='']">
Aici a fost candva un fiu (vizibil)
</xsl:template>

</xsl:stylesheet>
De data aceasta, xsl:apply-templates caută dacă există reguli pentru fiii produselor și, când o găsește pe
a doua (aplicabilă fiilor de tip element și fiilor de tip text vizibil), o aplică. Conform acesteia, toți fiii din
produs care sunt ori element, ori nod text vizibil, vor fi înlocuiți cu textul din interiorul regulii.

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:template match="/comanda">
<xsl:copy>
<xsl:attribute name="client">Pop Ion</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

<xsl:template match="produs">
<xsl:copy>
<xsl:attribute name="PretNou">
<xsl:value-of select="@pret|pret"/>
</xsl:attribute>
<ID><xsl:value-of select="@id|id"/></ID>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>

<xsl:template match="text()[normalize-space(.)!='']"/>

</xsl:stylesheet>

Prima regulă:

 copiază o versiune goală a rădăcinii comanda,

 îi adaugă atributul client

 și o umple cu rezultatul eventualelor reguli care există pentru fiii săi.

Astfel se ajunge la a doua regulă care:


 realizează versiuni goale ale produselor,

 le creează atributul PretNou cu valoarea luată din prețurile existente (fie din atributul vechi pret,
fie din elementul pret)

 le adaugă subelemente ID cu valoarea luată din id-urile vechi (atribute sau elemente)

 și mai adaugă rezultatul eventualelor reguli care mai există pentru fiii produselor.

A treia regulă caută noduri text vizibile și le șterge. Ea e apelată atât de prima regulă (ștergând nodurile
600 și "Aceasta este o comanda" care s-ar crea datorită regulii implicite de conservare a textului) cât și
de a doua regulă (ștergând textul vechi din produse, care s-ar conserva datorită aceleiași reguli
implicite).

Concluzie: Rețineți regulile implicite din XSLT:

 Codul documentului original nu e conservat în mod implicit, necesită o regulă de conservare


(totuși, se conservă nodurile text în mod implicit, și acestea au nevoie adesea de reguli care să le
șteargă);

 Toate nodurile din documentul original care nu intră sub incidența nici unei reguli de substituire
se elimină, cu excepția nodurilor text care se conservă (și concatenează între ele);

 Dacă un același nod e afectat de mai multe reguli de substituire, i se aplică regula cu match-ul
cel mai specific (ex: match="comanda/produs" va avea prioritate față de match="produs", care e
mai specific decât match="node()").

Cum putem rula transformări XSLT fără Oxygen?

1. XSLT 1.0 e suportat de orice browser. Foaia de transformare se atașează documentului XML original
într-o manieră asemănătoare cu modul de atașare a unei foi de stiluri CSS, apoi pur și simplu se deschide
documentul original în browser și el va apare gata transformat.

2. În context Ajax, putem aplica XSLT din JavaScript asupra răspunsului XML venit de la server, pentru a
produce o bucată de cod HTML inserabilă în pagină.
 Procedura implică browser sniffing căci are sintaxă diferită de la un browser la altul;

 Putem folosi librăria Sarissa care maschează diferențele dintre browsere;

 Transformarea XSLT executată în JavaScript (deci în browser) poate substitui operațiile clasice de
manipulare a paginii prin JavaScript.

3. Putem aplica XSLT și pe server, inclusiv în PHP, astfel încât să livrăm spre JavaScript un răspuns gata
transformat, gata de inserat (de ex. cu Ajax.Updater).

 Avantaj: nu mai trebuie transferată și foaia XSLT de la PHP la JavaScript;

 Dezavantaj: e o abatere de la principiul Ajax ca sarcinile să se transfere pe cât posibil spre


browser, minimizând efortul serverului; în plus, dacă JavaScript controlează transformarea,
poate să folosească datele originale și la altceva.

 Transformarea XSLT executată în PHP e frecvent folosită când codul XML a sosit în PHP din surse
externe (de la un serviciu Web, alte servere, un depozit de documente) și dorim să folosim în
site doar anumite informații din codul original (ca și în cazul JavaScript, XSLT devine o alternativă
la operațiile clasice de manipulare DOM).

În practică, cel mai des întâlnite sunt transformările pe server, deoarece documentele XML circulă mai
mult între servere decât între server și client (unde JSON tinde să fie preferat).

În acest seminar exemplificăm doar prima variantă:

Fișierul original va fi:

<?xml version="1.0" encoding="UTF-8"?>


<?xml-stylesheet type="text/xsl" href="transf.xsl"?>
<Comanda Client="PopIon">
<Produs ID="P1" Pret="100">Televizor</Produs>
<Produs ID="P2" Pret="30">Ipod</Produs>
</Comanda>
A doua linie face legătura cu foaia de transformări. Dacă dorim ca, în loc de transformare, să aplicăm o
foaie de stiluri CSS, înlocuim doar fișierul din HREF. Pentru a funcționa legătura, salvați foaia de
transformări cu numele transf.xsl:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<html>
<body>
<h1>Comanda lui <xsl:value-of select="Comanda/@Client"/></h1>
<table border="1">
<tr>
<td>Denumire</td>
<td>Pret</td>
</tr>
<xsl:for-each select="Comanda/Produs">
<tr>
<td><xsl:value-of select="."/></td>
<td><xsl:value-of select="@Pret"/></td>
</tr>
</xsl:for-each>
<tr>
<td colspan="2" align="left">
Total:
<xsl:value-of select="sum(Comanda/Produs/@Pret)"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

Din acest moment, dacă deschideți fișierul XML în browser, veți vedea pagina HTML rezultată din
transformarea sa.

Lab 7

Procesare XML/JSON în JavaScript și PHP


La începutul semestrului am transferat cu ajutorul AJAX date structurate sub forma unui string cu valori
separate cu virgulă. În următoarele exemple vom folosi AJAX pentru a transfera structuri de date mai
complexe, serializate conform standardelor XML și JSON. Adesea aceste structuri de date sunt create pe
server din diverse surse – o bază de date sau un serviciu Web.

Construirea unui răspuns XML în PHP se poate realiza pe mai multe căi:

 preluare dintr-un fișier XML existent;

 inițializare cu un string PHP care are o structură internă bine formată;

 construire nod cu nod a unui arbore DOM (metodă folosită dacă datele provin din baza de date
și trebuie convertite în XML);

 generarea de cod XML nou dintr-un fișier XML existent, printr-o transformare XSLT sau XQuery.

Dintre acestea primele două sunt mai simple, ultimele două însă sunt mult mai frecvent folosite,
deoarece cel mai des XMLul se construiește din date obținute din alte surse (bază de date, servicii Web,
depozit de documente), mai rar este disponibil gata de utilizare într-un string sau fișier. Sunt totuși și
situații în care XMLul e disponibil pe server în fișiere, de exemplu când se lucrează cu depozite de
documente XML (am sugerat la curs că ele pot teoretic substitui funcționalitatea unei baze de date
relaționale).

Odată ajuns în JavaScript, datele pot fi extrase din răspunsul XML prin:

 funcțiile standardului DOM;

 transformări XSLT (caz în care și foaia de transformare trebuie cerută de la server, preferabil tot
prin AJAX pentru a nu întrerupe procesele din pagină!)

 funcțiile E4X (mai ușor, dar slab suportat de browsere);

 interogări Xpath;

Vom prezenta câteva din aceste combinații posibile, plus metoda în care datele sunt transferate în
format JSON, care e cea mai facilă, dar nu are posibilități la fel de complexe (nu putem aplica
transformări XSLT sau validări pe JSON, ci apelăm doar la programare clasică – parcurgere de vectori și
obiecte).
Varianta server 1. Codul XML e disponibil pe server într-un string

Varianta cea mai simplă este atunci când avem deja codul XML stocat într-un string PHP (scrieți-l pe un
singur rând ca să nu apară noduri invizibile și salvați cu numele xml.php):

<?php

header('Content-type:application/xml');

$sirxml='<?xml version="1.0" encoding="UTF-8"?><Comanda Client="PopIon"><Produs ID="P1"


Pret="100">Televizor</Produs><Produs ID="P2" Pret="30">Ipod</Produs></Comanda>';

print $sirxml;

?>

Obs:

 header setează tipul de răspuns pe care PHP îl dă browserului – dacă nu setăm tipul XML,
browserul îl va recepționa ca text simplu și va trebui realizat efort suplimentar în JavaScript
pentru a-l converti în XML;

Puteți verifica faptul că în browser ajunge un arbore DOM și nu un simplu string, accesând direct scriptul
de mai sus (http://localhost/xml.php), apoi eliminați linia header și vedeți cum arată răspunsul în
browser în lipsa declarației Content-type!)

Construim o pagină HTML care să solicite acest cod XML prin Prototype, la trecerea mouseului peste un
DIV:

<!DOCTYPE html>

<html>

<head>

<script src="scriptaculous/lib/prototype.js"></script>
<script>

function cheamaxml()

config={method:"GET",onSuccess:proceseaza}

new Ajax.Request("xml.php",config)

function proceseaza(xhr)

alert(xhr.responseText)

</script>

</head>

<body>

<div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()">

Treci cu mouseul pe aici pentru a chema documentul XML de la server

</div>

<div id="x">Aici se va insera informatia din XML</div>

</body>

</html>

Obs:

 am folosit Ajax.Request din Prototype pentru schimbul asincron;

 nu am pus parameters, căci nu trimitem nimic la server, doar solicităm răspunsul acestuia (deci
e un răspuns static, care nu e influențat de trimiterea de date din browser);

 răspunsul e afișat ca text simplu, cu responseText.


În continuare vom trata codul ca XML, folosind responseXML ceea ce are ca efect o conversie automată
a stringului în arbore DOM, pentru a putea extrage datele prin metode specifice DOM.

În general se practică 4 metode pentru extragerea de date:

 metoda standard – cu funcțiile DOM standard – getElementsByTagName (dar fără


getElementById!!!), childNodes, firstChild, nodeValue,nodeName,getAttribute etc.

o avantaj: funcționează similar în toate browserele,

o dezavantaj: necesită uneori parcurgeri anevoioase, traversări de arbore și combinații de


structuri FOR / IF pentru a extrage datele care ne interesează (mai ales că nu putem
folosi getElementById);

 metoda XSLT:

o avantaj: putem genera cod HTML fără a interacționa (prea mult) cu arborele DOM al
paginii (mare pare din rolul JavaScript e preluat de XSLT);

o dezavantaj: trebuie să transferăm de la server și o foaie XSLT cu regulile de transformare


(uneori e mai eficient să facem transformarea direct în PHP și să dăm răspunsul gata
transformat).

 metoda Xpath:

o avantaj: interogări mult mai concise decât funcțiile DOM, și cu rezultate mai complexe;

o dezavantaj: funcțiile JavaScript care găzduiesc interogările (și le preiau rezultatele) sunt
încă prost implementate (diferă mult între browsere, au sintaxă greoaie);

 metoda E4X:

o avantaj: sintaxă foarte simplă, de tip JSON;

o dezavantaj: nesuportat de IE, incomplet suportat de alte browsere; dacă tot dorim să
folosim o sintaxă simplificată, lucrăm de la bun început cu JSON.

Varianta client 1. Extragerea datelor în JavaScript cu standardul DOM

În pagina HTML, în funcția de procesare a răspunsului, folosiți mesaje alert pentru a testa diverse
metode de extragere a datelor din arborele DOM al răspunsului XML (testați alertele pe rând, câte una,
eu le scriu pe toate în aceeași funcție, cu explicații sub formă de comentarii):
function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

alert(radacina.nodeName)

//afiseaza numele radacinii - Comanda

alert(radacina.firstChild.nodeName)

//afiseaza numele primului fiu al radacinii - Produs

alert(radacina.firstChild.nodeValue)

//afiseaza valoarea primului fiu al radacinii - null!!

//în standardul DOM continutul textual nu e considerat VALOARE, ci NOD fiu!

alert(radacina.firstChild.firstChild.nodeValue)

//abia acum se afiseaza textul primului element - Televizor

alert(radacina.getAttribute('Client'))

//afiseaza valoarea atributului radacinii - PopIon

alert(radacina.getElementsByTagName('Produs')[0].childNodes[0].nodeValue)

alert(radacina.getElementsByTagName('Produs')[1].childNodes[0].nodeValue)

/*afiseaza continuturile textuale ale celor doua produse: Televizor, apoi Ipod;

din nou, vedeti necesitatea de a accesa textul ca nod fiu ( cu firstChild sau, mai general, vectorul
childNodes), căci în DOM textul nu e considerat valoare a elementului, ci nod fiu*/

alert(radacina.getElementsByTagName('Produs')[0].firstChild.nodeName)

//afiseaza numele nodului text din primul Produs - #text, acesta e numele standard al nodurilor text

alert(radacina.getElementsByTagName('Produs')[0].parentNode.nodeName)

//afiseaza numele parintelui primului element Produs – Comanda

alert(radacina.childNodes[0].nextSibling.getAttribute('ID'))

//afiseaza valoarea atributului ID pentru fratele urmator primului fiu al radacinii - P2


alert(radacina.childNodes.length)

//afiseaza numarul de copii ai radacinii - 2

alert(radacina.firstChild.attributes.length)

//afiseaza numarul de atribute al primului fiu al radacinii - 2

alert(radacina.firstChild.attributes[1].value)

/*afiseaza valoarea celui de-al doilea atribut al primului fiu al radacinii - 100

deci e o alternativa la radacina.firstChild.getAttribute(…), utila atunci

cand nu cunoastem numele atributului*/

alert(radacina.firstChild.attributes[1].name)

//afiseaza numele celui de-al doilea atribut al radacinii - Pret

alert(radacina.hasAttributes())

//afiseaza true daca radacina are atribute - true

alert(radacina.hasAttribute('Client'))

//afiseaza true daca radacina are atributul Client - true

alert(radacina.firstChild.firstChild.hasChildNodes())

//indica daca primul fiu al primului fiu al radacinii are proprii fii - false

alert(raspuns.nodeType)

//afiseaza tipul raspunsului - 9 (cod pentru nodul document in ansamblul sau)

alert(radacina.nodeType)

//afiseaza tipul radacinii - 1 (cod pentru toate nodurile de tip element)

alert(radacina.firstChild.firstChild.nodeType)

//afiseaza tipul primului fiu al primului fiu al radacinii - 3 (nod de tip text)

alert(radacina.firstChild.attributes[1].nodeType)

//afiseaza tipul unui atribut - 2 (nod de tip atribut)


alert(radacina.firstChild.firstChild.length)

//afiseaza lungimea continutului textual al primului fiu al radacinii - 9

alert(radacina.firstChild.firstChild.isElementContentWhitespace)

//afiseza true la nodurile text care sunt invizibile; foarte utila la eliminarea lor! – aici false

alert(radacina.getElementById('P1').firstChild.nodeValue)

/*afiseaza continutul produsului cu ID P1 - null!

standardul DOM nu recunoaste atributul ID, decat daca acesta e insotit de un vocabular ce il declara ca
fiind de tip ID; cum obiectul XHR nu executa validare XML, nu putem folosi getElementById!!!*/

Obs:Spre deosebire de arborele DOM al paginii HTML, unde getElementById e foarte util, aici nu poate fi
folosit datorită limitării explicate în comentariu. Suntem nevoiți să căutăm IDul pe alte căi- înlocuiți
ultima alertă cu un ciclu FOR:

produse=radacina.getElementsByTagName('Produs')

for (i=0;i<produse.length;i++)

if (produse[i].getAttribute('ID')=='P1')

alert(produse[i].firstChild.nodeValue)

…sau, dacă tot avem Prototype la îndemână, cu o funcție iteratoare ce detectează primul element (din
vector) care respectă o condiție

produse=radacina.getElementsByTagName('Produs')

prod=$A(produse).find(testeaza)

//trece toate elementele din produse prin functia testeaza pana cand testeaza returneaza true
alert(prod.firstChild.nodeValue)

//afiseaza continutul textual al produsului gasit

…evident, e nevoie să definim și funcția-argument prin care trec toate elementele vectorului:

function testeaza(elem)

return elem.getAttribute('ID')=='P1'

Obs importante:

 funcția $A(), oferită de Prototype, convertește un obiect într-un vector; poate părea
redundant, căci știm că funcția getElementsByTagName returnează vectori oricum, dar lucrul
acesta e valabil doar pe arborele paginii (HTML DOM) nu și pe arborele răspunsului XML (aici
funcția returnează un obiect-colecție – chiar dacă putem să-l parcurgem cu FOR, nu putem să-l
trecem printr-o funcție iteratoare până nu facem o conversie explicită de la obiect la vector!).

 nu putem folosi optimizările sintatice Prototype în locul funcțiilor DOM (nu va funcționa $$()
în locul lui getElementsByTagName, când e vorba de XML); chiar dacă au același nume,
funcțiile DOM se comportă puțin diferit când le executăm asupra paginii HTML și când le
executăm asupra unui cod XML.

Rezumat:

 textele dintr-un cod XML nu sunt considerate valori ale elementelor ce le conțin, ci valori ale
nodurilor-fiu (de tip text) ale elementelor respective!

 getElementById nu se poate folosi pe un arbore DOM provenit din XML (decât dacă e validat
față de un vocabular, operație pe care XHR nu o asigură);

 funcțiile optimizate Prototype nu se pot folosi asupra arborelui DOM provenit din XML…;

 …în schimb funcțiile standard DOM pot fi folosite și asupra paginii HTML! (dar de obicei nu e
nevoie, avem funcțiile Prototype sau JavaScript mai ușor de folosit)
 câteva instrumente esențiale:

o childNodes, firstChild, lastChild – vectorul fiilor, primul și ultimul fiu;

o nodeValue, nodeName, nodeType – valoarea, numele, tipul unui nod

o attributes – vectorul atributelor;

o value sau getAttribute,name,nodeType – valoarea, numele, tipul unu atribut

o length (aplicat lui childNodes sau attributes) – numărul de noduri sau atribute

o parentNode – acces la nodul părinte;

o nextSibling,previousSibling – acces la fratele următor sau precedent;

o hasAttribute, hasAttributes, hasChildNodes, isElementContentWhitespace – teste de


existență a componentelor unui nod.

Toate aceste exemple sunt funcții de ACCES (ele obțin informații de la arbore, dar nu îl modifică).
Standardul DOM oferă și o gamă largă de funcții de MANIPULARE (care modifică structura arborelui
DOM) – acestea sunt importante acolo unde se construiește cod XML, ceea ce se întâmplă mai des pe
server decât în JavaScript. Totuși, destul de frecvent se face asta și în JavaScript, când dorim să
construim cod HTML prin metode XML, ca alternativă la inserarea de conținut nou cu innerHTML!

Vom modifica exemplul astfel încât, în locul multiplelor alerte, să generăm în pagină (în DIVul gol cu
ID='x') un tabel HTML de forma:

Comanda lui PopIon

Denumire Pret
Televizor 100
Ipod 30
Total:130

a. Inserarea datelor în pagină cu JavaScript clasic

Înlocuim funcția de procesare a răspunsului cu următoarea:

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

//citeste raspunsul

client=radacina.getAttribute('Client')

titlu="<h1>Comanda lui "+client+" </h1>"

//creeaza titlul cu informatia luata din atributul radacinii XML

tabel="<table border='1'><tr><td>Denumire</td><td>Pret</td></tr>"

total=0

//initializeaza codul HTML al tabelului (cu capul de tabel) si totalul cu zero

produse=radacina.getElementsByTagName('Produs')

//selecteaza vectorul produselor

for (i=0;i<produse.length;i++)

{
tabel=tabel+"<tr><td>"+produse[i].firstChild.nodeValue+"</td>"

tabel=tabel+"<td>"+produse[i].getAttribute("Pret")+"</td></tr>"

total=total+parseInt(produse[i].getAttribute("Pret"))

//pentru fiecare produs gasit, adauga un rand nou la codul HTML al tabelului si actualizeaza totalul

subsol="<tr><td colspan='2' align='right'>Total:"+total+"</td></tr></table>"

//creeaza sfarsitul tabelului, cu totalul

locatie=document.getElementById("x")

locatie.innerHTML=titlu+tabel+subsol

//concateneaza toate fragmentele in proprietatea innerHTML a DIVului rezervat in acest scop

Obs:

 parseInt converteste un string intr-un numar (avand in vedere ca valorile extrase din XML sunt
implicit tratate ca stringuri);

 exemplul s-a bazat pe inserarea de continut nou cu innerHTML

b.Inserarea datelor în pagină nod cu nod (funcții DOM standard găzduite de JavaScript)

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement
client=radacina.getAttribute("Client")

titlu=document.createElement("h1")

//s-a creat un element H1 in memorie

text=document.createTextNode("Comanda lui"+client)

//s-a creat un nod text (in memorie)

titlu.appendChild(text)

//s-a creat zona de titlu,lipind nodul text in interiorul nodului H1

tabel=document.createElement("table")

tabel.setAttribute("border","1")

captabel=document.createElement("tr")

celula=document.createElement("td")

textcelula=document.createTextNode("Denumire")

celula.appendChild(textcelula)

captabel.appendChild(celula)

//s-a creat prima celula in capul de tabel

celula=document.createElement("td")

textcelula=document.createTextNode("Pret")

celula.appendChild(textcelula)

captabel.appendChild(celula)

//s-a creat a doua celula in capul de tabel

tabel.appendChild(captabel)
//s-a adaugat capul de tabel la tabel

total=0

produse=radacina.getElementsByTagName('Produs')

for (i=0;i<produse.length;i++)

rand=document.createElement("tr")

//s-a creat un rand gol

celula=document.createElement("td")

text=document.createTextNode(produse[i].firstChild.nodeValue)

celula.appendChild(text)

rand.appendChild(celula)

//s-a creat prima celula din rand

celula=document.createElement("td")

text=document.createTextNode(produse[i].getAttribute("Pret"))

celula.appendChild(text)

rand.appendChild(celula)

//s-a creat a doua celula din rand

tabel.appendChild(rand)

//s-a adaugat randul la tabel

total=total+parseInt(produse[i].getAttribute("Pret"))
}

subsol=document.createElement("tr")

celula=document.createElement("td")

celula.setAttribute("colspan","2")

celula.setAttribute("align","right")

text=document.createTextNode("Total:"+total)

celula.appendChild(text)

subsol.appendChild(celula)

tabel.appendChild(subsol)

//s-a creat si adaugat ultimul rand la tabel

locatie=document.getElementById("x")

locatie.appendChild(titlu)

locatie.appendChild(tabel)

//se insereaza in DIV titlul, apoi tabelul

Obs importante:

Se observă că adăugarea de conținut nod cu nod e mai anevoioasă decât inserarea cu innerHTML. Totuși
e importantă din 2 motive:

 innerHTML putem folosi doar când:

o avem un loc rezervat în pagină (un DIV gol) gata să primească noul conținut;

o nou conținut e disponibil sau poate fi creat (prin concatenări) sub formă de string;

 când nu există un nod gol rezervat iar conținutul nu e disponibil ca string, avem variantele:
o să facem inserare ca ultim fiu (ne poziționăm pe părintele locației în care inserăm și
folosim appendChild);

o să facem inserare ca frate precedent (ne poziționăm pe fratele înaintea căruia vrem să
inserăm și folosim insertBefore);

o folosim Prototype care ne oferă Element.insert ce permite poziționare ca prim copil, ca


ultim copil, ca frate precedent sau ca frate următor (în funcție de cum reușim să ne
poziționăm).

Metoda cu funcții DOM standard este mai des folosită la nivelul serverului (PHP) unde se construiesc
adesea răspunsuri XML în această manieră.

Am văzut la început funcțiile de ACCESARE (citire) a informațiilor din arbore. Acum observați funcțiile de
MANIPULARE (scriere):

 createElement, createTextNode (apelate de obiectul document – dacă inserăm conținut în


pagina HTML sau de la nivelul rădăcinii – dacă inserăm conținut în cod XML);

o începătorii fac adesea gafa de a crede că aceste funcți asigură și alipirea nodurilor la
document! alipirea se face cu funcțiile de mai jos:

 appendChild, insertBefore (apelate la nivel de nod) – prin acestea se "lipesc" noduri noi la
arborele DOM; appendData, insertData (la nivel de nod text) – se adaugă text suplimentar în
nod;

 replaceChild (apelat de la nivelul unui element), replaceData(apelat de la nivelul unui nod text)
– permite substituirea conținutului;

 removeChild (de la nivel de element) - permite eliminarea de noduri;

 setAttribute, removeAttribute (de la nivel de element) – creează, modifică, respectiv distruge un


atribut.

Toate aceste funcți pot returna nodul creat/inserat/modificat/sters (dacă vrem să-i aducem ulterioare
modificări)! De exemplu dacă vrem să mutăm un nod dintr-o poziție în alta ne putem baza că
removeChild ni-l returnează după ce îl șterge din poziția originală, deci îl putem reinsera ușor în altă
poziție (fără să-l mai creăm o dată).

Aceste funcții nu pot fi combinate cu innerHTML! innerHTML funcționează:


 DOAR pe elemente care au fost deja alipite la conținutul paginii (indiferent cum);

 DOAR pentru a insera conținut stocat ca string.

c.Inserarea datelor în pagină cu facilități Prototype

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

client=radacina.getAttribute('Client')

titlu=new Element("h1")

titlu.update("Comanda lui"+client)

//crearea titlului

tabel=new Element("table",{border:1})

rand=new Element("tr")

celula=new Element("td")

celula.update("Denumire")

rand.insert(celula)

celula=new Element("td")

celula.update("Pret")

rand.insert(celula)

tabel.insert(rand)

//crearea capului de tabel


total=0

produse=radacina.getElementsByTagName('Produs')

vectproduse=$A(produse)

vectproduse.each(genereaza)

//atasarea unei functii iteratoare care genereaza randurile tabelului

subsol=new Element("tr")

celula=new Element("td",{align:"right",colspan:2})

celula.update("Total:"+total)

tabel.insert(celula)

//crearea subsolului

$('x').insert(titlu)

$('x').insert(tabel)

//inserarea in DIV

//in continuare, urmeaza functia iteratoare care genereaza randurile tabelului si calculeaza totalul

function genereaza(elem)

rand=new Element("tr")

celula=new Element("td")

celula.update(elem.firstChild.nodeValue)

rand.insert(celula)

celula=new Element("td")
celula.update(elem.getAttribute("Pret"))

rand.insert(celula)

tabel.insert(rand)

total=total+parseInt(elem.getAttribute("Pret"))

Obs.:

 funcțiile update (substituire de conținut) și insert (adăugare) pot fi folosite pe noduri XML la fel
ca pe noduri HTML (ca alternativă la appendChild/insertBefore)! cele două funcții pot fi
apelate în oricare din formele:

o Element.update(țintă,conținut-nou)

o țintă.update(conținut-nou)

o $('id-țintă').update(conținut-nou)

 new Element() e o alternativă la document.createElement() – are avantajul că ne permite să


definim atributele chiar la momentul creării (nu mai trebuie apelată setAttribute)

 după cum s-a văzut deja, funcția $A e necesară pentru a face ca vectorul rezultat din
getElementsByTagName să poată fi procesat de Prototype (în acest caz cu o funcție
iteratoare).

Varianta client 2. Extragerea cu XSLT

O altă metodă de a insera datele răspunsului XML în pagina Web e să aplicăm o transformare XSLT la
nivelul browserului. Toate browserele moderne suportă transformări XSLT 1.0 (bazate pe interogări
Xpath 1.0) dar legat de versiunea 2.0 viitorul este incert.

O primă problemă e că transformarea XSLT trebuie adusă de la server tot într-o manieră asincronă, deci
prin Ajax.Request (dacă lucrăm pe Prototype). Deci vom avea două comunicări asincrone – una care
aduce răspunsul XML și una care aduce foaia XSLT. Odată ajunse ambele în browser, executăm
transformarea.
Pas1. Salvăm următoarea foaie XSLT pe server (în htdocs) cu numele transformare.xsl:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<h1>Comanda lui <xsl:value-of select="Comanda/@Client"/></h1>
<table border="1">
<tr>
<td>Denumire</td>
<td>Pret</td>
</tr>
<xsl:for-each select="Comanda/Produs">
<tr>
<td><xsl:value-of select="."/></td>
<td><xsl:value-of select="@Pret"/></td>
</tr>
</xsl:for-each>
<tr>
<td colspan="2" align="left">
Total:
<xsl:value-of select="sum(Comanda/Produs/@Pret)"/>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>

Pas2. Modificăm scriptul xml.php: dacă primește prin GET variabila "foaie", deschide de pe disc foaia
XSLT și o livrează browserului. Dacă nu, livrează codul XML din exemplele precedente, luat din același
string (nu dați Enteruri în el):

<?php

header('Content-type:application/xml');

if (isset(GET["foaie"])
{

$foaie=new DOMDocument();

$foaie->load('transformare.xsl');

print $foaie->saveXML();

else

$sirxml='<?xml version="1.0" encoding="UTF-8"?><Comanda Client="PopIon"><Produs ID="P1"


Pret="100">Televizor</Produs><Produs ID="P2" Pret="30">Ipod</Produs></Comanda>';

print $sirxml;

?>

Pas3. Scriem pagina client (scrieți una nouă, căci vom reveni la cea precedentă):

<!DOCTYPE html>

<html>

<head>

<script src="scriptaculous/lib/prototype.js"></script>

<script>

function cheamaxml()

config={method:"GET",onSuccess:cheamaxslt}

new Ajax.Request("xml.php",config)

/*
S-a solicitat raspunsul XML de la xml.php.

La sosirea raspunsului se apeleaza functia cheamaxslt, pentru a aduce si foaia XSLT

*/

function cheamaxslt(xhr)

raspunsxml=xhr.responseXML

config={method:"GET",parameters:"foaie",onSuccess:proceseaza}

new Ajax.Request("xml.php",config)

/*

S-a solicitat foaia XSLT, folosind variabila foaie, fara valoare - e suficient ca e trimisa ( caci scriptul PHP
testeaza doar existenta ei, cu if (isset($_GET['foaie'])) )

La sosirea foii se apeleaza functia proceseaza, responsabila cu executia transformarii.

*/

function proceseaza(xhr)

raspunsxslt=xhr.responseXML

if (Prototype.Browser.IE)

rezultat=raspunsxml.transformNode(raspunsxslt)

$("x").update(rezultat)

// aceasta a fost varianta pentru IE

else

procesorxslt=new XSLTProcessor()
procesorxslt.importStylesheet(raspunsxslt)

rezultat=procesorxslt.transformToFragment(raspunsxml,document)

$("x").appendChild(rezultat)

// aceasta a fost varianta pentru FF

</script>

</head>

<body>

<div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()">

Treci cu mouseul pe aici pentru a chema documentul XML de la server

</div>

<div id="x">Aici se va insera informatia din XML</div>

</body>

</html>

Obs:

 observați modul în care am înlănțuit cele 2 Ajax.Request, cu ajutorul evenimentului onSuccess:


când termină de venit răspunsul XML, se inițiază solicitarea pt foaia XSLT, iar când termină de
venit și aceasta, se apelează procedura de transformare;

o în acest fel ne asigurăm că transformarea nu e executată înainte să fi ajuns ambele


componente la browser și că nu există riscul unui conflict între cele 2 comunicări
asincrone;

 deoarece avem Prototype, am folosit Prototype.Browser.IE pentru browser sniffing, pentru a


detecta dacă ne aflăm în IE:

o dacă nu lucrăm cu Prototype, se poate face un test if (document.all), deoarece


document.all e un obiect existent doar în IE;
 în varianta IE am putut folosi funcția update pentru a insera rezultatul transformării în pagină;

o aceasta deoarece în IE transformarea returnează text! (deci se poate folosi și


innerHTML, dacă nu avem Prototype);

 în varianta FF nu merge cu update sau innerHTML, trebuie să inserăm rezultatul cu


appendChild sau insertBefore:

o aceasta deoarece în FF transformarea returnează un fragment (=un grup de noduri


XML) și ar fi un efort inutil să facem conversia în text.

Varianta client 4. Extragerea cu Xpath

Extragerea datelor cu Xpath direct din JavaScript e teoretic facilă (în ce privește limbajul Xpath) dar
practic e destul de complicată (datorită funcțiilor JavaScript care găzduiesc interogările Xpath și le
gestionează rezultatele). În plus, sunt diferențe mari între browsere în ce privește aceste funcții.

Reveniți la pagina client așa cum arăta înainte de exemplul cu XSLT (cu un singur Ajax.Request). Scriptul
PHP poate sa rămână cel cu ultimele modificari, căci conține un IF care ne va da codul XML inițial cu
condiția să nu trimitem variabila GET "foaie").

Vom rescrie funcția proceseaza() pentru a extrage diverse informații din XML cu XPath.

Următoarele exemple NU vor funcționa în Internet Explorer:

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

rez=raspuns.evaluate('count(Produs)',radacina,null,XPathResult.ANY_TYPE,null)

/*detalii:
- functia trebuie apelata de catre documentul pe care se face interogarea (aici, raspuns);

- primul argument e interogarea efectiva (relativa, de obicei);

- al doilea argument e nodul curent (fata de care se calculeaza calea relativa);

- al treilea argument e mai putin important (pentru XML cu prefixe/spatii de nume);

- al patrulea argument e un cod ce indica tipul dorit pt rezultat (XPathResult.ANY_TYPE va returna:

- tipul numeric pentru functii precum count si sum

- tipul string pentru functii ce returneaza stringuri

- tipul boolean pentru functii precum boolean sau not

- tipul iterator pentru interogari ce returneaza noduri sau colectii de noduri (chiar daca e returnat un
singur nod sau atribut!)

- ultimul argument e neimportant (e variabila in care sa se stocheze rezultatul, dar in cazul de fata
indicam asta prin atribuire)

- odata ce s-a obtinut un rezultat, valoarea acestuia se extrage prin diferite metode in functie de tipul
rezultatului

*/

alert("tipul rezultatului este "+rez.resultType)

/*va afisa 1, codul corespunzator tipului numeric; tipul numeric e returnat de:

- functii XPath precum count sau sum (dacă am pus argumentul ANY_TYPE)

- orice interogări ce returnează valori/noduri numerice (dacă am pus argumentul NUMBER_TYPE)

*/

alert("nr de produse este "+rez.numberValue)

//va indica valoarea numerica a rezultatului - 2 (numarul de produse)

rez=raspuns.evaluate('name(//@*[.=100])',radacina,null,XPathResult.ANY_TYPE,null)

alert("tipul rezultatului este "+rez.resultType)

/*va afisa 2, codul corespunzator tipului string; tipul string e returnat de:
- functii XPath ce returnează stringuri (dacă am pus argumentul ANY_TYPE)

- orice interogări ce returnează valori/noduri textuale (dacă am pus argumentul STRING_TYPE)

*/

alert("atributul cu valoarea 100 se numeste "+rez.stringValue)

//va afisa valoarea string a rezultatului - Pret (numele atributului cu valoarea 100)

rez=raspuns.evaluate('Produs="Televizor"',radacina,null,XPathResult.ANY_TYPE,null)

alert("tipul rezultatului este "+rez.resultType)

/*va afisa 3, codul corespunzator tipului boolean; tipul boolean e returnat de:

- functii XPath ce returnează valori booleene (dacă am pus argumentul ANY_TYPE)

- orice interogări ce returnează măcar o soluție (dacă am pus argumentul BOOLEAN_TYPE)

*/

alert("exista vreun Produs cu valoarea Televizor? "+rez.booleanValue)

//va afisa valoarea booleana a rezultatului - true (este adevarat ca exista un Produs cu valoarea
Televizor)

rez=raspuns.evaluate('Produs',radacina,null,XPathResult.ANY_TYPE,null)

alert("tipul rezultatului este "+rez.resultType)

/*va afisa 4, codul corespunzator tipului iterator (set de noduri); tipul iterator e returnat de orice
interogare care returnează noduri sau atribute (și nu rezultate de funcții XPath), chiar dacă s-a găsit o
singură soluție:

- după returnare, rezultatul se parcurge cu un ciclu FOR sau WHILE, care apeleaza in mod repetat functia
iterateNext() ce returneaza urmatorul nod din setul rezultat

*/

produs=rez.iterateNext()
while (produs)

alert("S-a gasit produsul cu pretul "+produs.getAttribute("Pret")+

" si continutul "+produs.firstChild.nodeValue)

produs=rez.iterateNext()

alert("S-au terminat produsele")

rez=raspuns.evaluate('..',radacina.firstChild.firstChild,null,XPathResult.ANY_TYPE,null)

alert("tipul rezultatului este "+rez.resultType)

/*va afisa 4, din nou rezultat iterator

- in acest caz am folosit ca nod curent primul nepot al radacinii, iar prin interogare am solicitat
parintele sau

*/

alert(rez.iterateNext().nodeName)

//va afisa Produs, numele parintelui primului nepot din radacina

rez=document.evaluate('div[@id]',document.body,null,XPathResult.ANY_TYPE,null)

alert("tipul rezultatului este "+rez.resultType)

/*acest exemplu demonstreaza ca XPAth se poate folosi si asupra paginii HTML!

diferențe:

- funcția e apelată de obiectul document (ce reprezintă pagina), nu de raspuns

- nodul curent va fi un element din pagina, de exemplu document.body

- datele din rezultat se pot extrage si cu innerHTML


*/

alert(rez.iterateNext().innerHTML)

//va afisa conținutul din DIVul care are ID ("Aici se va insera informatia din XML")

Pentru Internet Explorer implementarea e mult mai simplă. Diferențele principale sunt:

 se folosesc două funcții: selectNodes (pentru interogări care returnează mai multe noduri) sau
selectSingleNode (pentru interogări care returnează un nod);

 ambele funcții au un singur argument - interogarea XPath;

 ambele funcții returnează noduri XML (nu trebuie să indicăm tipul rezultatului);

 ambele funcții folosesc ca nod curent obiectul care le apelează (nu trebuie să indicăm nodul
curent);

 limitări:

o nu se pot executa interogări ce returnează valori calculate (cu funcții XPath) ci numai
interogări ce returnează noduri/valori de noduri;

o nu se pot interoga elemente din pagina HTML, cu numai din răspunsul XML.

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

raspuns.setProperty("SelectionLanguage","XPath")

/*aceasta linie e necesara, altfel IE va numerota nodurile incepand de la zero (iar XPath le numara de la
1!)*/
rez=radacina.selectNodes("Produs")

for (i=0;i<rez.length;i++)

alert("S-a gasit produsul cu pretul "+rez[i].getAttribute("Pret")+

" si continutul "+rez[i].firstChild.nodeValue)

//s-au returnat toate produsele și s-au parcurs afișând un mesaj pentru fiecare

rez=radacina.selectSingleNode("Produs[1]/@Pret")

alert(rez.value)

rez2=rez.selectSingleNode("..")

alert(rez2.nodeName)

/*s-a returnat pretul primului produs, care apoi a fost folosit ca nod curent (apelator) in urmatoarea
interogare, ce returneaza parintele acelui pret*/

Obs:

 În general se consideră că interogările XPath sunt mai rapide decât accesarea cu funcții DOM
standard (getElement….). De fapt numeroase frameworkuri AJAX (inclusiv Prototype)
apelează, în implementarea unor funcții precum $, $$, la XPath și nu la funcțiile getElement,
ceea ce face ca funcțiile Prototype, deși mult mai puternice, să aibă performanțe
asemănătoare cu getElementsByTagName).

 Totuși, dpdv sintactic, XPath încă nu e o soluție larg adoptată – diferențele mari între
browsere (ce necesită dublarea codului sursă) și complicațiile inutile ale sintaxei din Firefox au
descurajat în general adopția acestei tehnici ca alternativă la funcțiile standard DOM.

Varianta client 5.Extragerea cu E4X

Varianta E4X e o sintaxă JavaScript mai ușor de folosit decât funcțiile DOM:

radacina.Produs[0]

în loc de
radacina.firstChild.firstChild.nodeValue

radacina.Produs.@Pret

în loc de

radacina.firstChild.getAttribute('Pret')

Nu vom insista totuși asupra acestei sintaxe, deoarece IE refuză deocamdată să o implementeze, iar
celelalte browsere o implementează incomplet. Nu vom insista asupra acestei sintaxe, datorită lipsei de
viabilitate.

În general se consideră că sintaxa E4X nu se justifică, fiind oarecum similară cu:

 sintaxa căilor Xpath (cu condiția ca Xpath să își simplifice modul de executare și stocare a
rezultatelor!) – doar că se folosesc puncte în loc de slashuri;

 sintaxa JSON, caz în care e preferabil ca răspunsul să se dea de la bun început direct în JSON.

Din aceste motive popularitatea sintaxei E4X stagnează deocamdată.

Varianta server 2. Codul XML e disponibil pe server într-un fișier

Ultimele exemple au prezentat diferite moduri de procesare a răspunsului XML după ce acesta a ajuns la
browser. În continuare mutăm discuția la nivelul serverului, pentru a vedea cum lucrează PHP cu
structuri XML.

În cazurile precedente, codul XML a fost livrat de PHP în modul cel mai simplu, ca string. În coninuare
vom prelua codul XML dintr-un fișier existent pe server. Construiți următorul fișier cu Oxygen, și salvați-l
pe server în htdocs cu numele comanda.xml:

<?xml version="1.0" encoding="UTF-8"?>


<Comanda Client="PopIon">
<Produs ID="P1" Pret="100">Televizor</Produs>
<Produs ID="P2" Pret="30">Ipod</Produs>
</Comanda>

Principalele deosebiri față de cazul precedent sunt că:

 încărcăm fișierul într-un obiect de tip XML (clasă numită DOMDocument în PHP);

 de obicei fișierele create prin tastare conțin noduri invizibile (Enterurile, Taburile, spațiile
folosite în timpul tastării pentru aranjarea codului vor fi considerate noduri text).

Prezența nodurilor invizibile va afecta traversarea pe bază de frați (nextSibling, previousSibling) sau pe
bază de poziție (childNodes,firstChild,lastChild).

Important: Internet Explorer elimină automat nodurile invizibile, dar alte browsere nu!

Construim scriptul PHP (salvați-l cu numele xml2.php) care deschide acest fișier și îl trimite la browser:

<?php

header('Content-type:application/xml');

$cmdxml=new DOMDocument();

$cmdxml->load('comanda.xml');

print $cmdxml->saveXML();

?>

Obs:

- Se pot încărca și stringuri în obiectul XML, dar cu loadXML în loc de load

Construim pagina client, similară cu cea din primele exemple (dar acum solicită răspuns de la xml2.php),
și punem câteva alerte pentru a ne convinge de prezența câmpurilor invizibile:
<!DOCTYPE html>

<html>

<head>

<script src="scriptaculous/lib/prototype.js"></script>

<script>

function cheamaxml()

config={method:"GET",onSuccess:proceseaza}

new Ajax.Request("xml2.php",config)

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

alert(radacina.firstChild.nodeName)

//nu mai apare Produs, ci #text (numele standard al nodurilor text)!

alert(radacina.firstChild.nodeValue)

//apare valoarea invizibila a nodului

alert(radacina.childNodes.length)

//nu mai apare 2, ci 5 - inclusiv noduri invizibile

alert(radacina.getElementsByTagName('Produs')[0].nextSibling.nodeName)

//urmatorul frate al primului produs nu e tot un produs, ci un nod invizibil

alert(radacina.firstChild.isElementContentWhitespace)
//testeaza daca primul fiu al radacinii e un nod invizibil - true!

</script>

</head>

<body>

<div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()">

Treci cu mouseul pe aici pentru a chema documentul XML de la server

</div>

<div id="x">Aici se va insera informatia din XML</div>

</body>

</html>

Obs:

Se poate observa că în Internet Explorer, aceleași alerte dau alte rezultate (IE ignoră nodurile
invizibile!)

Pentru cazurile în care nodurile invizibile ne-ar putea influența, se practică 2 abordări:

 se creează o funcție recursivă ce parcurge tot arborele DOM și elimină nodurile invizibile
detectate;

 se creează o foaie XSLT care transformă arborele DOM într-unul echivalent, dar fără noduri
invizibile.

Ambele strategii se pot aplica atât la client (JavaScript), cât și la server (PHP). Adesea e preferabil să aibă
loc în PHP, pentru a nu se transfera degeaba octeți în plus, care oricum trebuie eliminați.

Vom exemplifica funcția recursivă în JavaScript și transformarea în PHP. Exemplele se pot adapta la orice
limbaj cu modificări minime (date de specificul sintactic).
Funcția recursivă de eliminare a nodurilor invizibile în JavaScript:

function eliminaNoduriInv(nod)

for (var i=0;i<nod.childNodes.length;i++) //se parcurg toți fiii

var fiu=nod.childNodes[i]

if (fiu.isElementContentWhitespace) //dacă fiul e nod invizibil, e șters

nod.removeChild(fiu)

i-- //contorul trebuie decrementat, caci stergerea a afectat numarul de fii!

if (fiu.nodeType==1)

eliminaNoduriInv(fiu) //dacă fiul e un element, i se aplică recursiv curățarea

return nod

Obs:

 funcția primește ca argument orice nod și returnează același nod, curățat de noduri invizibile;

 e important să apară cuvântul var! în programarea recursivă din JavaScript trebuie să-l punem
datorită particularității JavaScript că funcțiile își pot refolosi variabilele rămase de la apelul
precedent

o asta va avea efectul neplăcut că apelul recursiv va reutiliza contorul i de la apelul


precedent (primul ciclu FOR) și obținem ciclu infinit căci contorul va fi mereu resetat la
1!
o prezența lui var va asigura caracterul local al variabilelor, pentru ca apelurile repetate și
recursive să își creeze propriile variabile și să nu se bruieze reciproc!

 în limbajele în care nu e implementată funcția isElementContentWhitespace, se poate face un


test cu expresii regulate de genul if (nod.nodeType=3 && !(/\S/.test(nod.nodeValue));

o altfel spus, se testează dacă nodul este de tip text, iar valoarea lui are măcar un caracter
diferit de cele invizibile (e vorba de o expresie regulată cu codul \S ce reprezintă orice
caracter diferit de cele invizibile);

Pentru a testa succesul acestei funcții, vom afișa codul XML înainte și după transformare. Vom profita de
ocazie pentru a arăta și cum se poate face conversia din XML în string, în JavaScript. Și această operație
are diferențe mari între browsere:

function proceseaza(xhr)

raspuns=xhr.responseXML

radacina=raspuns.documentElement

if (Prototype.Browser.IE)

sirxml=radacina.xml

alert(sirxml)

/*daca ne aflam in IE nu are sens sa aplicam curatarea (totusi, in mesaje alert, codul XML arata ca si cum
n-ar fi fost curatat!)*/

else

serializator=new XMLSerializer()

sirxml=serializator.serializeToString(radacina)
alert("Inainte de curatare:\n "+sirxml)

radacinacurata=eliminaNoduriInv(radacina)

sirxml=serializator.serializeToString(radacinacurata)

alert("Dupa curatare:\n "+sirxml)

/*daca ne aflam in FF, afisam codul XML inainte si dupa curatare*/

Obs:

 din nou, am detectat browserul cu Prototype (reminder: în lipsa lui Prototype, testarea
existenței obiectului document.all duce la același rezultat);

 IE afișează codul XML ca și cum n-ar fi fost curățat (dar vă puteți convinge că a fost,
numărând/afișând nodurile din document prin alerte cu informații extrase din nodul DOM);

o conversia XML-string în IE e asigurată de proprietatea xml;

 FF îl afișează diferit înainte de curățate și după curățare:

o conversia XML-string în FF e asigurată de clasa XMLSerializer

Varianta server 3. Codul XML e obținut printr-o transformare XSLT pe server

Exemplificăm transformarea XSLT în PHP tot prin eliminarea de noduri invizibile:

Presupunem că avem pe server fișierul comanda.xml cu conținutul:

<?xml version="1.0" encoding="UTF-8"?>

<Comanda Client="PopIon">

<Produs ID="P1" Pret="100">Televizor</Produs>


<Produs ID="P2" Pret="30">Ipod</Produs>

</Comanda>

Avem și foaia XSLT care elimină noduri invizibile, salvată cu transformare2.xml:

<?xml version="1.0" encoding="UTF-8"?>


<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>

<xsl:template match="/">
<xsl:copy-of select="."/>
</xsl:template>

</xsl:stylesheet>

Scriptul PHP va fi:

<?php

header('Content-type:application/xml');

$sursa=new DOMDocument();

$sursa->load('comanda.xml');

$foaie=new DOMDocument();

$foaie->load('transformare2.xsl');

$procesor=new XSLTProcessor();

$procesor->importStylesheet($foaie);

$rezultat=$procesor->transformToXML($sursa);
print $rezultat;

?>

Obs:

 instrumentele sunt aceleași cu cele pe care le-am folosit în JavaScript pentru a executa
transformarea sub Firefox:

o clasa XSLTProcessor;

o metodele importStylesheet și transformToXML (ambele apelate de procesorul XSLT);

o rezultatul e text și poate fi returnat imediat cu print (fără saveXML).

Pentru a testa succesul operației:

 fie cuplați pagina client la acest script și afișați modul în care ajunge răspunsul acestui script în
Firefox (după cum am văzut, în IE nu se vede diferența în alerte);

 fie accesați direct acest script din localhost și vă uitați cum arată răspunsul în Firebug (în
browser codul XML va fi afișat formatat, doar în Firebug, la rubrica Net puteți vedea efectiv șirul
de caractere din care lipsesc nodurile invizibile).

Evident, în aceeași manieră putem executa în PHP transformarea ce producea tabelul HTML.

Varianta server 4. Codul XML e generat pe server, nod cu nod

În sfârșit, codul XML poate fi generat pe server și cu funcțiile standard DOM, prin construirea nod cu
nod. Metoda este ceva mai anevoioasă (am aplicat-o deja în JavaScript) dar e garantat că nu creează
noduri invizibile. E folosită adesea pentru a genera dinamic cod XML din date preluate din diverse surse
(array-uri, baze de date etc.)

PHP oferă mai multe clase pentru gestiunea și crearea de cod XML. Dintre acestea, două sunt mai des
folosite:
 funcțiile DOM standard (clasa DOMDocument);

 un set simplificat de funcții (clasa SimpleXMLElement).

Următoarele exemple testați-le direct în browser (http://localhost/fisier.php), fără a mai executa pagina
client (doar cât să vă convingeți cum arată documentul XML generat):

Metoda standard, cu Clasa DOMDocument:

<?php

header("Content-type:application/xml");

$raspuns=new DOMDocument();

$radacina=$raspuns->createElement("Comanda");

$radacina->setAttribute("Client","PopIon");

$produs=$raspuns->createElement("Produs");

$produs->setAttribute("ID","P1");

$produs->setAttribute("Pret","100");

$text=$raspuns->createTextNode("Televizor");

$produs->appendChild($text);

$radacina->appendChild($produs);

$produs=$raspuns->createElement("Produs");

$produs->setAttribute("ID","P2");

$produs->setAttribute("Pret","30");

$text=$raspuns->createTextNode("Ipod");
$produs->appendChild($text);

$radacina->appendChild($produs);

$raspuns->appendChild($radacina);

print $raspuns->saveXML();

?>

Observați linia boldată, prin care elementul rădăcină e inserat în nodul-document (rădăcina reală a
documentului).

Metoda simplificată, cu clasa SimpleXMLElement:

<?php

header("Content-type:application/xml");

$radacina=new SimpleXMLElement("<Comanda/>");

$radacina->addAttribute("Client","PopIon");

$produs=$radacina->addChild("Produs","Televizor");

$produs->addAttribute("ID","P1");

$produs->addAttribute("Pret","100");

$produs=$radacina->addChild("Produs","Ipod");

$produs->addAttribute("ID","P2");

$produs->addAttribute("Pret","30");
print $radacina->asXML();

?>

Comparați codul sursă și observați economia de sintaxă (aproape înjumătățită):

 nu mai trebuie să lipim elementul rădăcină la document (funcția asXML() de la final se ocupă de
asta implicit);

 crearea și alipirea unui nou element cu conținut textual simplu se face într-o singură linie, cu
addChild (dincolo erau 4 linii: crearea elementul, crearea textului, lipirea textului la element,
lipirea elementului la radacina).

Sintaxa SimpleXMLElement are și avantaje legate citirea de date:

$radacina->$Produs[0]

poate fi folosit pentru a accesa textul conținut în primul produs, ca alternativă la metoda standard:

$radacina->getElementsByTagName("Produs")[0]->firstChild->nodeValuesimplexmlelement

Varianta server 5. Răspunsul serverului e în format JSON

Dacă în loc de XML se preferă transferarea de date în format JSON, totul devine mult mai simplu, și mai
performant (dar nu există posibilități avansate precum transformarea XSLT, interogarea datelor,
validarea cu vocabulare).

În PHP, codul JSON se poate crea ușor din orice array existent.

Salvați următorul script pe server cu numele json.php:

<?php
header("Content-type:application/json");

$Produs1=array("ID"=>"P1","Pret"=>"100","Denumire"=>"Televizor");

$Produs2=array("ID"=>"P2","Pret"=>"30","Denumire"=>"Ipod");

$Produse=array($Produs1,$Produs2);

$Raspuns=array("Comanda"=>array("Client"=>"PopIon","Produse"=>$Produse));

print json_encode($Raspuns);

?>

Creați pagina client care solicită acest cod și generează un tabel cu denumirile și prețurile produselor:

<!DOCTYPE html>

<html>

<head>

<script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>

<script>

function cheamajson()

config={method:"GET",onSuccess:proceseaza}

new Ajax.Request("json.php",config)

function proceseaza(xhr)

eval("raspuns="+xhr.responseText)

client=raspuns.Comanda.Client

titlu="<h1>Comanda lui "+client+" </h1>"


tabel="<table border='1'><tr><td>Denumire</td><td>Pret</td></tr>"

total=0

produse=raspuns.Comanda.Produse

for (i=0;i<produse.length;i++)

tabel=tabel+"<tr><td>"+produse[i].Denumire+"</td>"

tabel=tabel+"<td>"+produse[i].Pret+"</td></tr>"

total=total+parseInt(produse[i].Pret)

subsol="<tr><td colspan='2' align='right'>Total:"+total+"</td></tr></table>"

locatie=document.getElementById("x")

locatie.innerHTML=titlu+tabel+subsol

</script>

</head>

<body>

<div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamajson()">

Treci cu mouseul pe aici pentru a chema raspunsul JSON de la server

</div>

<div id="x">Aici se va insera informatia din JSON</div>

</body></html>

Obs:

 funcția eval asigură conversia răspunsului JSON din text în obiect JavaScript; în practică e
recomandat ca în loc de eval să se folosească o bibliotecă de funcții JSON precum
https://github.com/douglascrockford/JSON-js, care are în plus un mecanism de securitate ce
verifică dacă nu cumva în răspunsul JSON există și comenzi JavaScript (care s-ar executa automat
de către eval);

 observați avantajele sintactice:

raspuns.Comanda.Client în loc de raspuns.documentElement.getAttribute("Client")

raspuns.Comanda.Produse[0].Pret în loc de
raspuns.documentElement.getElementsByTagName("Produs")[0].getAttribute("Pret")

 avantajele de performanță pot fi măsurate atât în Firebug (numărul de biți și timpul de transfer
comparativ cu XML) sau prin cronometrarea în JavaScript a procedurii de generare a tabelului (în
cazul cronometrării avantajele nu vor fi semnificative pe aceste exemple datorită numărului mic
de date transferate/interogate).

S-ar putea să vă placă și