222
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

utilizarea internetului in afaceri cursuri

Embed Size (px)

DESCRIPTION

utilizarea internetului in afaceri

Citation preview

Page 1: utilizarea internetului in afaceri cursuri

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.

Page 2: utilizarea internetului in afaceri cursuri

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">

function schimbDate(codpostal)

{

xhr=creareXHR()

xhr.onreadystatechange=procesare

xhr.open("GET","codpostal.php?CP="+codpostal)

xhr.send(null)

}

Crearea obiectului XHR

Fixarea unei funcţii (fără paranteze!) de prelucrare a răspunsului, pentru cand acesta va sosi

Trimiterea argumentului la scriptul codpostal.php, cu codul concatenat ca variabilă GET

Page 3: utilizarea internetului in afaceri cursuri

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()

return xhr

}

Browser sniffing:

- încearcă crearea obiectului XHR în Internet Explorer mai vechi şi mai nou

- dacă încercările au eşuat, testează existenţa clasei XMLHttpRequest şi în caz afirmativ creează obiectul

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)

Page 4: utilizarea internetului in afaceri cursuri

function procesare()

{

if (xhr.readyState == 4)

if (xhr.status == 200)

{

raspuns=xhr.responseText

date=raspuns.split(',')

document.getElementById("TXoras").value=date[0]

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

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

}

else

{

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

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

}

}

</script>

</head>

Prelucrarea raspunsului:

- Raspunsul e asteptat ca text brut cu 3 valori delimitate de virgula (split sparge stringul intr-un vector de 3 elemente, folosind virgula ca separator)

- Datele sunt atribuite campurilor existente in formularul

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ă!!!

Page 5: utilizarea internetului in afaceri cursuri

<body>

<h1>Introduceti datele</h1>

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

<table>

Page 6: utilizarea internetului in afaceri cursuri

<tr>

<td>Nume</td>

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

</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>

<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 colspan="2"><input type="submit" value="Trimite formular" ></td>

</tr>

</table>

</form>

Campurile ce vor fi completate cu raspunsul serverului(vezi IDurile cu care sunt accesate din functia de procesare)

Paragraf gol, rezervat pentru mesajul de eroare(O variantă ar fi ca paragraful să conţină de la început mesajul de eroare, dar să fie ascuns cu proprietatea CSS "display:none")

Campul ce va declansa schimbul de date la momentul ONBLUR

Page 7: utilizarea internetului in afaceri cursuri

<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

Page 8: utilizarea internetului in afaceri cursuri

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!

Page 9: utilizarea internetului in afaceri cursuri

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).

Page 10: utilizarea internetului in afaceri cursuri
Page 11: utilizarea internetului in afaceri cursuri

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()

}

}

function trimite()

{

creareXHR()

Extragerea explicită a datelor din formular

Page 12: utilizarea internetului in afaceri cursuri

valori=new Object()

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

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

valori["adresa"]=document.getElementById("TXadresa").value

valori["oras"]=document.getElementById("TXoras").value

valori["judet"]=document.getElementById("TXjudet").value

sir=""

for (cheie in valori)

{

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

}

xhr.open("POST","raspuns.php")

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>

Concatenarea datelor din formular într-un QueryString cu conversie de caractere nepermise

Setarea antetului HTTP pentru trimiterea de formulare

Formularul NU are METHOD, ACTION si buton SUBMIT! Rolul acestora e preluat de XHR

Page 13: utilizarea internetului in afaceri cursuri

<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>

Page 14: utilizarea internetului in afaceri cursuri

<tr>

<td></td>

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

</tr>

</table>

</form>

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

</body>

</html>

Page 15: utilizarea internetului in afaceri cursuri

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>

<head>

<title>Formular</title>

<script type="text/javascript">

function initializari()

{

tinta=document.getElementById('d')

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()

Funcţia de iniţializare creează obiectul XHR şi pregăteşte DIVul în care se va insera răspunsul serverului

Funcţiile care configurează obiectul XHR pentru comunicare cu 2 scripturi diferite, şi 2 funcţii de procesare diferite

Page 16: utilizarea internetului in afaceri cursuri

}

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)

}

function procesare1()

{

if (xhr.readyState == 4)

if (xhr.status == 200)

{

Funcţiile care configurează obiectul XHR pentru comunicare cu 2 scripturi diferite, şi 2 funcţii de procesare diferite

Page 17: utilizarea internetului in afaceri cursuri

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()"/>

Page 18: utilizarea internetului in afaceri cursuri

<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

Page 19: utilizarea internetului in afaceri cursuri

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

Page 20: utilizarea internetului in afaceri cursuri

{

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()

}

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

xhr.onreadystatechange=function()

{

if (xhr.readyState != 4) return;

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

}

Browser sniffing pt IE si Mozilla

Trimiterea variabilelor a si b spre script.php

Am folosit o sintaxa alternativa, cu o functie ANONIMA

Browser sniffing pt IE si Mozilla

Page 21: utilizarea internetului in afaceri cursuri

xhr.send(null)

}

</script>

</head>

<body>

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

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

</body>

</html>

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/>";

Daca raspunsul e ok (readystate=4) se adauga raspunsul la continutul existent al DIVului "mesaj" (pe un rand nou, vezi <br>-ul)

Obs: testul de status 200 lipseste, pt a scurta exemplul

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

DIV gol rezervat pt raspunsul serverului

Page 22: utilizarea internetului in afaceri cursuri

?>

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.

Page 23: utilizarea internetului in afaceri cursuri

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);

Page 24: utilizarea internetului in afaceri cursuri

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>

<head>

<script type="text/javascript">

function trimite(cod)

{

cadru=document.getElementById("cadruint")

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

Modificarea dinamica a atributului SRC al cadrului, pentru ca acesta sa acceseze scriptul server vizat. URLul scriptului are atasate date de tip GET, deci modificarea lui SRC are ca efect si trimiterea datelor!

Page 25: utilizarea internetului in afaceri cursuri

}

function citestedate(cdr)

{

documentcadru=cdr.contentWindow.document

raspuns=documentcadru.body.innerHTML

procesare(raspuns)

}

function procesare(rasp)

{

vector=rasp.split(",")

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>

<h1>Introduceti datele</h1>

<form >

<table>

Extragerea continutului cadrului

Extragerea continutului lui BODY din continutul cadrului(liniile cu rosu sunt nucleul tehnicii pull – pagina mare trage datele din cadru)

Procesarea raspunsului si includerea sa in pagina (in formular)

Crearea cadrului:

pt a-l face invizibil, ar trebui dimensiuni zero, dar l-am lasat vizibil pentru a-i putea monitoriza continutul; SRC initial e vid;

are ev. Onload, care apeleaza functia de extragere a datelor in momentul in care au sosit date in cadru (de la server)

Page 26: utilizarea internetului in afaceri cursuri

<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>

Declansarea transferului prin evenimentul Blur

Page 27: utilizarea internetului in afaceri cursuri

</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:

Page 28: utilizarea internetului in afaceri cursuri

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)

{

cadru=document.getElementById("cadruint")

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

}

function procesare(rasp)

{

vector=rasp.split(",")

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

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

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

}

</script>

Trimiterea datelor, prin manevrarea atributului SRC al cadrului

Procesarea raspunsului (desi pagina client nu contine nici un apel al acestei functii!)

Page 29: utilizarea internetului in afaceri cursuri

</head>

<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>

Cadrul (fara onLoad si fara functie de extragere a datelor!)

Page 30: utilizarea internetului in afaceri cursuri

<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)

Page 31: utilizarea internetului in afaceri cursuri

}

</script>

</head>

<body onload=impingedate()>"

.$raspuns.

"</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>

Liniile cu rosu sunt nucleul tehnicii push: codul generat pentru a umple cadrul se ocupa: - de impingerea datelor spre pagina parinte la momentul onLoad (la sosirea datelor in cadru)- de apelarea functiei de procesare din pagina parinte

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

Page 32: utilizarea internetului in afaceri cursuri

<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)

{

raspuns=obiectxhr.responseText

vector=raspuns.split(',')

for (i=0;i<vector.length;i++)

{

camp=$('desubstituit'+i)

camp.setValue(vector[i])

}

}

function date(cod)

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

setValue e o versiune specializată (pentru elemente INPUT) a metodei setAttribute din JavaScript.

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

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

Page 33: utilizarea internetului in afaceri cursuri

{

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

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

}

</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>

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

Page 34: utilizarea internetului in afaceri cursuri

<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>

</form>

<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>

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

</script>

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

Funcţia ce se va aplica fiecărui element dintr-un vector (vectorul INPUT-urilor în care inserăm valori).

Page 35: utilizarea internetului in afaceri cursuri

<script>

function substituie(x,pozitie)

{

x.setValue(vector[pozitie])

}

function proceroare(obiectxhr)

{

Element.insert('formular',{after:'<div style="color:green"> Mesaj eroare:'+obiectxhr.responseText+'</div>'})

}

function procesareraspuns(obiectxhr)

{

raspuns=obiectxhr.responseText

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 eroarea este inserată cu Element.insert (O altă variantă este să se creeze un DIV ascuns şi să fie activat cu Elemen.show sau Element.toggle)

Acum câmpurile sunt găsite pe bază de CLASS şi sunt trecute toate prin funcţia de substituire a valorii, cu ajutorul funcţiei iteratoare each!

Important: each transferă 2 valori spre funcţia-argument substituie: elementul şi poziţia sa în vector(vezi argumentele cu care s-a creat substituie())

Page 36: utilizarea internetului in afaceri cursuri

</script>

</head>

<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>

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

Page 37: utilizarea internetului in afaceri cursuri

<tr>

<td>Judet</td>

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

</tr>

<tr>

<td></td>

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

</tr>

</table>

</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)

Nu am mai rezervat un DIV pentru inserarea erorii, el este CREAT de Element.insert (în timp ce Element.update îi substituia doar conţinutul!)

Page 38: utilizarea internetului in afaceri cursuri

{

document.getElementById("Eroare").innerHTML="<font color=red>Eroare"+obiectxhr.responseText+"</font>"

}

function date(cod)

{

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

{

method: "get",

parameters: "CodPostal="+cod,

onFailure: proceroare

})

}

</script>

</head>

<body>

<h1>Introduceti datele</h1>

<form>

<table id=t1>

<tr>

<td>Nume</td>

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

</tr>

<tr>

Fara onSuccess! Continutul e inserat automat!

(onSuccess e necesar doar daca vrem sa se intample si altceva)

Marcatorul al carui continut se substituie

Page 39: utilizarea internetului in afaceri cursuri

<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)

Page 40: utilizarea internetului in afaceri cursuri

<?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';

}

$cod=$_GET["CodPostal"];

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>

Serverul rescrie integral conţinutul tabelului, cu noile date inserate

(parţial redundant!)

Page 41: utilizarea internetului in afaceri cursuri

<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!

Page 42: utilizarea internetului in afaceri cursuri

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)

}

Page 43: utilizarea internetului in afaceri cursuri

</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.

Page 44: utilizarea internetului in afaceri cursuri

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)

Page 45: utilizarea internetului in afaceri cursuri

}

</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).

Page 46: utilizarea internetului in afaceri cursuri

Î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>

Page 47: utilizarea internetului in afaceri cursuri

<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:

Page 48: utilizarea internetului in afaceri cursuri

#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})

Page 49: utilizarea internetului in afaceri cursuri

...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

Page 50: utilizarea internetului in afaceri cursuri

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.

Page 51: utilizarea internetului in afaceri cursuri

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'})

Page 52: utilizarea internetului in afaceri cursuri

}

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;}

Page 53: utilizarea internetului in afaceri cursuri

</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:

Page 54: utilizarea internetului in afaceri cursuri

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;

Page 55: utilizarea internetului in afaceri cursuri

}

Ș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.

Page 56: utilizarea internetului in afaceri cursuri

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!)

Page 57: utilizarea internetului in afaceri cursuri

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.

Page 58: utilizarea internetului in afaceri cursuri

Î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'];

Page 59: utilizarea internetului in afaceri cursuri

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.

Page 60: utilizarea internetului in afaceri cursuri

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).

Page 61: utilizarea internetului in afaceri cursuri

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",

Page 62: utilizarea internetului in afaceri cursuri

{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:

Page 63: utilizarea internetului in afaceri cursuri

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.

Page 64: utilizarea internetului in afaceri cursuri

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()

{

Page 65: utilizarea internetului in afaceri cursuri

//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

Page 66: utilizarea internetului in afaceri cursuri

//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>

Page 67: utilizarea internetului in afaceri cursuri

<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:

Page 68: utilizarea internetului in afaceri cursuri

<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()

{

Page 69: utilizarea internetului in afaceri cursuri

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)

{

Page 70: utilizarea internetului in afaceri cursuri

$('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

Page 71: utilizarea internetului in afaceri cursuri

<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)

Page 72: utilizarea internetului in afaceri cursuri

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>

Page 73: utilizarea internetului in afaceri cursuri

<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()

{

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

}

</script>

</head>

</html>

<body onload="initializare()">

<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"];

E preferabil ca toate editoarele InPlace din pagină să fie iniţializate la încărcarea paginii (desigur, ele pot fi iniţializate şi la primul click pe comentariu).

Doar IDul şi scriptul server sunt obligatorii. Acoladele conţin un obiect JSON cu opţiuni suplimentare (aici dimensiunile casetei)

Page 74: utilizarea internetului in afaceri cursuri

?>

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):

Page 75: utilizarea internetului in afaceri cursuri

<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()

Page 76: utilizarea internetului in afaceri cursuri

{

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"/>

Page 77: utilizarea internetului in afaceri cursuri

- 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

Page 78: utilizarea internetului in afaceri cursuri

(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;

Page 79: utilizarea internetului in afaceri cursuri

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.

Page 80: utilizarea internetului in afaceri cursuri

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.

Page 81: utilizarea internetului in afaceri cursuri

<?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

Page 82: utilizarea internetului in afaceri cursuri

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>

Page 83: utilizarea internetului in afaceri cursuri

<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.

Page 84: utilizarea internetului in afaceri cursuri

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:

Page 85: utilizarea internetului in afaceri cursuri

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ţă).

Page 86: utilizarea internetului in afaceri cursuri

Documentul(nodul document)

comanda(elementul document)

<?xml ...?>(nod de tip PI)

Nod text invizibil

Nod comentariu produs(nod element) produs

Id=”p1”

produs(nod element) produs

produs(nod element) produs

Nod text invizibil

Nod text invizibil

Nod text invizibil

Aceasta este o comanda de produse

(nod text vizibil+invizibil)

Calculator(nod text vizibil)

id(nod element) produs

Nod text

invizibil

denumire(nod element) produs

Nod text

invizibil

pret(nod element) produs

Nod text

invizibil

Nod text

invizibil

Id=”p2” pret=”100”

denumire=”Televizor” pret=”200”

p3(nod text vizibil)

300(nod text vizibil)

Ipod(nod text vizibil)

prettotal(nod element) produs

Nod text invizibil

600(nod text vizibil)

Probleme legate de arborele DOM

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

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

Page 87: utilizarea internetului in afaceri cursuri

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?

Page 88: utilizarea internetului in afaceri cursuri

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:

Page 89: utilizarea internetului in afaceri cursuri

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).

Page 90: utilizarea internetului in afaceri cursuri

Î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;

Page 91: utilizarea internetului in afaceri cursuri

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).

Page 92: utilizarea internetului in afaceri cursuri

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.

Page 93: utilizarea internetului in afaceri cursuri

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/*

Page 94: utilizarea internetului in afaceri cursuri

...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

Page 95: utilizarea internetului in afaceri cursuri

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).

Page 96: utilizarea internetului in afaceri cursuri

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:

Page 97: utilizarea internetului in afaceri cursuri

/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)

Page 98: utilizarea internetului in afaceri cursuri

//*[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()

Page 99: utilizarea internetului in afaceri cursuri

...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!

Page 100: utilizarea internetului in afaceri cursuri

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()]/..

Page 101: utilizarea internetului in afaceri cursuri

..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)

Page 102: utilizarea internetului in afaceri cursuri

//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())

Page 103: utilizarea internetului in afaceri cursuri

... 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)

Page 104: utilizarea internetului in afaceri cursuri

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)

Page 105: utilizarea internetului in afaceri cursuri

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)

Page 106: utilizarea internetului in afaceri cursuri

../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)

Page 107: utilizarea internetului in afaceri cursuri

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);

Page 108: utilizarea internetului in afaceri cursuri

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).

Page 109: utilizarea internetului in afaceri cursuri

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;

Page 110: utilizarea internetului in afaceri cursuri

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

Page 111: utilizarea internetului in afaceri cursuri

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.

Page 112: utilizarea internetului in afaceri cursuri

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

Page 113: utilizarea internetului in afaceri cursuri

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

Page 114: utilizarea internetului in afaceri cursuri

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:

Page 115: utilizarea internetului in afaceri cursuri

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

Page 116: utilizarea internetului in afaceri cursuri

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:

Î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).:

Tipurile

Vocabularul propriu-zis

Page 117: utilizarea internetului in afaceri cursuri

<?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:

Page 118: utilizarea internetului in afaceri cursuri

<?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);

Page 119: utilizarea internetului in afaceri cursuri

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

Page 120: utilizarea internetului in afaceri cursuri

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>

Page 121: utilizarea internetului in afaceri cursuri

Ș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:

Page 122: utilizarea internetului in afaceri cursuri

<xsl:template match="/">

Text oarecare

</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.

match este calea Xpath a elementelor care vor suferi substituţia

conţinutul lui xsl:template este noul conţinut care va intra în locul celui detectat de match.

Page 123: utilizarea internetului in afaceri cursuri

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).

Page 124: utilizarea internetului in afaceri cursuri

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;

Page 125: utilizarea internetului in afaceri cursuri

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):

Page 126: utilizarea internetului in afaceri cursuri

<?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>

Page 127: utilizarea internetului in afaceri cursuri

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)

Page 128: utilizarea internetului in afaceri cursuri

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.

Page 129: utilizarea internetului in afaceri cursuri

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

Page 130: utilizarea internetului in afaceri cursuri

<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">

Page 131: utilizarea internetului in afaceri cursuri

<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)

Page 132: utilizarea internetului in afaceri cursuri

{

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!).

Page 133: utilizarea internetului in afaceri cursuri

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).

Page 134: utilizarea internetului in afaceri cursuri

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"/>

Page 135: utilizarea internetului in afaceri cursuri

<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).

Page 136: utilizarea internetului in afaceri cursuri

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.

Page 137: utilizarea internetului in afaceri cursuri

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>

Page 138: utilizarea internetului in afaceri cursuri

#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:

Page 139: utilizarea internetului in afaceri cursuri

<?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>

Page 140: utilizarea internetului in afaceri cursuri

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:

Page 141: utilizarea internetului in afaceri cursuri

<?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">

Page 142: utilizarea internetului in afaceri cursuri

<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.

Page 143: utilizarea internetului in afaceri cursuri

<?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)

Page 144: utilizarea internetului in afaceri cursuri

ş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.

Page 145: utilizarea internetului in afaceri cursuri

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"

Page 146: utilizarea internetului in afaceri cursuri

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.

Page 147: utilizarea internetului in afaceri cursuri

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):

Page 148: utilizarea internetului in afaceri cursuri

<?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)

Page 149: utilizarea internetului in afaceri cursuri

}

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.

Page 150: utilizarea internetului in afaceri cursuri

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)

Page 151: utilizarea internetului in afaceri cursuri

//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)

Page 152: utilizarea internetului in afaceri cursuri

/*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

Page 153: utilizarea internetului in afaceri cursuri

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:

Page 154: utilizarea internetului in afaceri cursuri

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

Page 155: utilizarea internetului in afaceri cursuri

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 PretTelevizor 100Ipod 30Total:130

a. Inserarea datelor în pagină cu JavaScript clasic

Înlocuim funcţia de procesare a răspunsului cu următoarea:

Page 156: utilizarea internetului in afaceri cursuri

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

Page 157: utilizarea internetului in afaceri cursuri

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)

Page 158: utilizarea internetului in afaceri cursuri

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++)

Page 159: utilizarea internetului in afaceri cursuri

{

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")

Page 160: utilizarea internetului in afaceri cursuri

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);

Page 161: utilizarea internetului in afaceri cursuri

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.

Page 162: utilizarea internetului in afaceri cursuri

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')

Page 163: utilizarea internetului in afaceri cursuri

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)

Page 164: utilizarea internetului in afaceri cursuri

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"

Page 165: utilizarea internetului in afaceri cursuri

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');

Page 166: utilizarea internetului in afaceri cursuri

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

*/

Page 167: utilizarea internetului in afaceri cursuri

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)

Page 168: utilizarea internetului in afaceri cursuri

}

// 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);

Page 169: utilizarea internetului in afaceri cursuri

î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);

Page 170: utilizarea internetului in afaceri cursuri

- 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)

*/

Page 171: utilizarea internetului in afaceri cursuri

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")+

Page 172: utilizarea internetului in afaceri cursuri

" 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")

Page 173: utilizarea internetului in afaceri cursuri

}

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")+

Page 174: utilizarea internetului in afaceri cursuri

" 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

Page 175: utilizarea internetului in afaceri cursuri

î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ă:

Page 176: utilizarea internetului in afaceri cursuri

î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>

Page 177: utilizarea internetului in afaceri cursuri

<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>

Page 178: utilizarea internetului in afaceri cursuri

<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)

{

Page 179: utilizarea internetului in afaceri cursuri

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);

Page 180: utilizarea internetului in afaceri cursuri

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)

}

}

Page 181: utilizarea internetului in afaceri cursuri

/*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"

Page 182: utilizarea internetului in afaceri cursuri

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;

Page 183: utilizarea internetului in afaceri cursuri

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:

Page 184: utilizarea internetului in afaceri cursuri

<?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();

?>

Page 185: utilizarea internetului in afaceri cursuri

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);

Page 186: utilizarea internetului in afaceri cursuri

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);

?>

Page 187: utilizarea internetului in afaceri cursuri

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)

Page 188: utilizarea internetului in afaceri cursuri

}

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).