Sunteți pe pagina 1din 189

Comunicare asincrona, browser sniffing, XHR, Firebug

Reminder teoretic: AJAX= HTML: pt structurarea paginii, de obicei prin DIV si SPAN, dar nu obligatoriu (important: recapitulai de la grafic diferenele ntre DIV i SPAN!) + CSS: pt formatarea elementelor din pagin, inclusiv poziionare, ascundere etc. (recapitulai CSS de la grafic!) + JavaScript: pt. modificarea structurii HTML i a proprietilor 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 pri 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 situaiile care nu pot fi implementate prin XHR, cum ar fi uploadul de fiiere, gestiunea butoanelor Back-Forward din browser etc.) +XML: acesta e formatul standard n care serverul ar trebui s i mpacheteze rspunsul; ns nu e obligatoriu, n practic se prefer ca serverul s rspund 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 aa 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 rspund cu stringuri la cererile ce vin de la browser (orice server face asta). Deoarece suntei familiarizai cu PHP vom folosi scripturi PHP simple pentru a genera rspunsul. Considerai ca tem s modificai exemplele astfel nct n PHP rspunsul s fie construit cu date dintr-o baz de date MySQL.

Urmtorul exemplu prezint mecanismul de baz al comunicrii asincrone.

Se creeaz un formular ce solicit adresa potal a utilizatorului. Imediat dup ce codul potal e tastat, la momentul trecerii n urmtoarea caset, codul potal e trimis la server, care rspunde cu strada i celelalte detalii pe care le are stocate despre acel cod potal.

Pagina Client (fisier de tip HTML):

<html> <head> <title>Formular</title> <script type="text/javascript"> Crearea obiectului XHR Fixarea unei funcii (fr paranteze!) de prelucrare a rspunsului, pentru cand acesta va sosi Trimiterea argumentului la scriptul codpostal.php, cu codul concatenat ca variabil GET

function schimbDate(codpostal) { xhr=creareXHR() xhr.onreadystatechange=procesare xhr.open("GET","codpostal.php?CP="+codpostal) xhr.send(null) }

- dac ncercrile au euat, testeaz existena clasei XMLHttpRequest i n caz afirmativ creeaz obiectul

Browser sniffing: function creareXHR() { try {xhr=new ActiveXObject("Msxml2.XMLHTTP")} catch (e) { try {xhr=new ActiveXObject("Microsoft.XMLHTTP")} catch (e) {xhr=false} } - dac ncercrile au euat, testeaz existena clasei XMLHttpRequest i n caz afirmativ creeaz obiectul - ncearc crearea obiectului XHR n Internet Explorer mai vechi i mai nou

if (!xhr&&typeof(XMLHttpRequest)!='undefined') xhr=new XMLHttpRequest() return xhr }

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

function procesare() { if (xhr.readyState == 4) if (xhr.status == 200) Prelucrarea raspunsului: { 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> 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!!! - 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

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

Campul ce va declansa schimbul de date la momentul ONBLUR

<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> Paragraf gol, rezervat pentru mesajul de eroare (O variant ar fi ca paragraful s conin de la nceput mesajul de eroare, dar s fie ascuns cu proprietatea CSS "display:none") Campurile ce vor fi completate cu raspunsul serverului (vezi IDurile cu care sunt accesate din functia de procesare)

<p id="Eroare"></p> </body> </html>

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

<?php if ($_GET["CP"]=="400451") print "Cluj Napoca,Cluj,Aleea Azuga"; else print "cod incorect,cod incorect,cod incorect"; ?>

Dup cum indic scriptul server, acesta poate raspunde cu date corecte doar la codul 400451! In rest, se returneaza "cod incorect" (intr-un scenariu realist, ar exista o baza de date cu toate codurile si strazile/orasele asociate lor) Verificati si inserarea erorii in pagina (prin oprirea serverului ceea ce va afecta codurile de raspuns 4/200) Urmariti consola Firebug pentru a monitoriza datele schimbate intre client si server

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

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

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

Obs2: Legat de linia xhr.onreadystatechange=procesare:

De ce nu se pun paranteze la numele functiei? Daca s-ar pune, functia s-ar executa automat cand scriptul executa acea linie si nu ar mai astepta pana la aparitia evenimentului!

Detalii: in JavaScript exista notiunea de "variabila de tip functie", adica o variabila a carei valoare e corpul unei functii (si nu valoarea returnata!). Cu ajutorul variabilelor de tip functie se pot realiza diverse trucuri de programare: - schimbarea numelui unei functii (prin simple atribuiri de variabile) - crearea de functii care primesc ca argument alte functii - crearea de functii care returneaza functii In principiu, cand vedeti o functie fara paranteze (ex: procesare in loc de procesare()) inseamna ca e vorba de o variabila ce contine corpul functiei si nu de valoarea returnata) EX: function f() {} a=f Din acest moment, functia f se poate apela si cu a()

Mai mult, se poate atribui direct o functie unei variabile: a=function {}

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

Obs3: - spre deosebire de siteurile clasice, datele trimise prin GET nu se mai vad in bara de adresa; - erorile din scriptul server nu se mai manifesta printr-un mesaj de eroare afisat in browser (de genul syntax error on line x) mesajul e stocat in obiectul XHR (putem sa-l inseram in pagina).

Sarcina de lucru: Sa se extinda scriptul pentru a cauta codul postal intr-o baza de date a codurilor postale (cu 5 inregistrari).

Urmtorul 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

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) } Setarea antetului HTTP pentru trimiterea de formulare Concatenarea datelor din formular ntr-un QueryString cu conversie de caractere nepermise

function procesare() { if (xhr.readyState!=4) return alert(xhr.responseText) }

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

<body> <h1>Introduceti datele</h1> <form> <table> <tr> <td>Nume</td> <td><input type=text id=TXnume ></td> </tr> <tr> <td>CodPostal</td> <td><input type=text id=TXcodpostal></td> </tr> <tr> <td>Adresa</td> <td><input type=text id=TXadresa size=50></td> </tr> <tr> <td>Oras</td> <td><input type=text id=TXoras ></td> </tr> <tr> <td>Judet</td> <td><input type=text id=TXjudet ></td> </tr>

<tr> <td></td> <td><input type="button" value="Trimite" onclick="trimite()" ></td> </tr> </table> </form> <div id="rasp"></div> </body> </html>

Urmtorul exemplu arat cum trebuie procedat cnd aceeai pagin trebuie s realizeze mai multe comunicri asincrone n timpul utilizrii sale. n general e valabil acest lucru, deoarece paginile Web complexe suport diverse evenimente i fiecare eveniment poate declana comunicri asincrone cu diverse scripturi server - spre deosebire de siteurile clasice unde un formular poate primi rspunsuri doar de la o pagin PHP (pe de alt parte, acea pagin poate implica mai multe pagini folosind require i include; aici ns acelai script JavaScript poate dialoga cu mai multe scripturi PHP prin ACELAI obiect XHR).

Pagina de mai jos comunic cu 2 scripturi PHP i pentru fiecare din acestea aloc o alt funcie de procesare a rspunsului (chiar dac acestea sunt aproape identice n cazuri realiste acestea difer semnificativ).

<html> <head> <title>Formular</title> <script type="text/javascript"> Funcia de iniializare creeaz obiectul XHR i pregtete DIVul n care se va insera rspunsul serverului

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() Funciile care configureaz obiectul XHR pentru comunicare cu 2 scripturi diferite, i 2 funcii de procesare diferite

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

Funciile care configureaz obiectul XHR pentru comunicare cu 2 scripturi diferite, i 2 funcii de procesare diferite

raspuns=xhr.responseText tinta.innerHTML=raspuns } else alert('Eroare conexiune 1') }

function procesare2() { if (xhr.readyState == 4) if (xhr.status == 200) { raspuns=xhr.responseText tinta.innerHTML=raspuns } else alert('Eroare conexiune 2') }

</script> </head>

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

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

Observaie important: cnd tim c pagina HTML va dialoga de mai multe ori, chiar cu mai multe scripturi server, NU are rost s crem obiectul XHR de FIECARE dat.

Ar trebui s tii (de la grafic) c JavaScript are particularitatea c variabilele create de o funcie rmn accesibile i celorlalte funcii, nu se distrug. Asta nseamn c la onload putem apela o funcie de iniializare care s creeze obiectul XHR i orice alte variabile vor fi necesare n continuare, apoi evenimentele din pagin s apeleze funcii ce folosesc obiectele existente, fr 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 coninutul 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, rspunsul 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 rspuns flagul s fie comutat pentru a indica eliberarea obiectului XHR:

raspuns=xhr.responseText

xhrliber=true

Apoi naintea fiecrui 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 funcia de iniializare, mpreun cu cte un flag pentru fiecare, apoi la iniierea fiecrei comunicri asincrone se testeaz mai nti 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 bazm pe mai mult de 2 obiecte XHR transfernd date n paralel, cci unele browsere nu suport mai mult de 2 transferuri simultane spre acelai server (recent Firefox a mrit la 6 aceast limit). Lab2 Urmatorul exemplu numara cate cereri HTTP se fac dinspre browser spre server, in sesiunea curenta. Pagina client: <html> <head> <script type="text/javascript"> var xhr function modifica() { try

{ xhr = new ActiveXObject("Msxml2.XMLHTTP") } catch (e) { try { xhr = new ActiveXObject("Microsoft.XMLHTTP") } catch (e) Browser sniffing pt IE si Mozilla { xhr = false } } Trimiterea variabilelor a si b spre script.php

if (!xhr && typeof XMLHttpRequest !="undefined") { xhr = new XMLHttpRequest() }

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

xhr.onreadystatechange=function() { if (xhr.readyState != 4) return;

Am folosit o sintaxa alternativa, cu o functie ANONIMA

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

xhr.send(null) } </script> </head> DIV gol rezervat pt raspunsul serverului 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

<body> <div id="mesaj"></div> <button onmouseover="modifica()">Click Me</button> </body> </html> Declansarea comunicarii asincrone - e specificat evenimentul (MouseOver) - e specificata functia ce instantiaza XHR si se ocupa de schimbul de date

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

<?php session_start(); if (isset($_SESSION["a"])) $_SESSION["a"]++; else $_SESSION["a"]=1; //daca nu exista, variabila se initializeaza cu 1 //se verifica daca exista variabila in sesiune; daca da, se incrementeaza

print "Aceasta e conectarea nr.".$_SESSION["a"]."din sesiunea curenta<br/>"; print " Datele sosite la server sunt: ".$_SERVER["QUERY_STRING"]."<br/>";

?>

Observatii: Scriptul numara dialogurile care au loc intre client si server (la primul schimb creeaza o variabila in sesiune, apoi o tot mareste la fiecare cerere HTTP); Pe langa textul care indica numarul conectarii asincrone, se returneaza si datele sosite la server, citite direct din $_SERVER["QUERY_STRING"] (puteau fi luate si una cate una din $_GET["a"] sau $_GET["b"]); Atentie, numaratoarea continua chiar si dupa Refresh scriptului (datorita sesiunii), dar si dupa restartarea browserului! Si chiar dupa restartarea Apacheului!

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

Versiunile recente ale browserelor au implementat un mecanism automat de a transfera sesiunea intrun 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 lum nici una din aceste msuri, nu mai este nici o diferen (de comportament) ntre sesiune i cookie.

Urmrii consola Firebug n timpul transferului:

Extindei scriptul PHPla varianta:

<?php session_start();

if (isset($_COOKIE["b"])) { setcookie("b",$_COOKIE["b"]+1,time()+3600); print "<b>Aceasta e revenirea cu nr.".$_COOKIE["b"]." a acestui calculator<b><br>"; } else { setcookie("b",1,time()+3600);

print "<b>Aceasta e prima conectare a acestui calculator<b><br>"; }

if (isset($_SESSION["a"])) $_SESSION["a"]++; else $_SESSION["a"]=1; print "Aceasta e conectarea nr.".$_SESSION["a"]." din sesiunea curenta<br>"; print " Datele sosite la server sunt: ".$_SERVER["QUERY_STRING"]."<br>"; if ($_SESSION["a"]==10) session_destroy(); ?>

De data aceasta se fac 2 numrtori n paralel: una prin cookie, care msoar de cte ori a revenit acelai calculator; una prin sesiune, care msoar 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!

function citestedate(cdr) { documentcadru=cdr.contentWindow.document raspuns=documentcadru.body.innerHTML procesare(raspuns) }


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

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>


Procesarea raspunsului si includerea sa in pagina (in formular)

</head> <body> <iframe id=cadruint width=400 height=50 src="" onload="citestedate(this)"> </iframe> <h1>Introduceti datele</h1> <form > <table>
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)

<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

</table> </form> </body> </html>

Scriptul server (salvat cu numele codpostal.php): <?php if ($_GET["CodPostal"]==400451) print "Cluj Napoca,Cluj, Aleea Azuga...(completati detaliile)"; else print "cod incorect, cod incorect, cod incorect"; ?>

De urmarit si ce se intampla in Firebug:

A se observa:

cadrul nu a fost facut invizibil (pentru a vedea in clar raspunsul serverului); in Firebug nu se mai poate folosi rubrica Console (aceasta monitorizeaza doar obiectul XHR) dar se poate folosi rubrica Net (care monitorizeaza traficul dintre client si server, oferind toate detaliile schimbului de date) spre deosebire de XHR, in corpul HTML trebuie tratate doua evenimente: o onLoad (aplicat cadrului, pentru a apela functia de procesare a raspunsului atunci cand raspunsul a sosit); in varianta XHR, partea aceasta era inlocuita de handlerul onReadyStateChange; 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 }
Trimiterea datelor, prin manevrarea atributului SRC al cadrului

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>


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

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

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

<td><input type=text id=TXcodpostal onblur="trimite(this.value)"></td> </tr> <tr> <td>Adresa</td> <td><input type=text id=TXadresa size=50></td> </tr> <tr> <td>Oras</td> <td><input type=text id=TXoras ></td> </tr> <tr>

<td>Judet</td> <td><input type=text id=TXjudet ></td> </tr> <tr> <td></td> <td><input type=submit value=Trimite ></td> </tr> </table> </form> </body> </html> Scriptul server (raspunspush.php)

<?php

if ($_GET["CodPostal"]==400451) $raspuns="Cluj Napoca,Cluj, Aleea Azuga...(completati detaliile)"; else $raspuns="cod incorect, cod incorect, cod incorect";

print "<html><head><script> function impingedate() { continutcadru=document.body.innerHTML parent.procesare(continutcadru)

} </script> </head> <body onload=impingedate()>" .$raspuns. "</body></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

?>

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

Urmtorul exemplu rezolv din nou scenariul cu codul potal, de data aceasta folosind mecanismul Ajax.Request i clasa Element, oferite de frameworkul Prototype.

Pentru aceasta, downloadai mai nti frameworkul Scriptaculous (care conine i Prototype n folderul lib): http://script.aculo.us/downloads. Dai folderului numele scriptaculous i copiai-l n htdocs.

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

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

<script>

function proceroare(obiectxhr) { Element.update("Eroare","Mesaj eroare:"+obiectxhr.responseText) Element.setStyle("Eroare",{color:'green'}) }

function procesareraspuns(obiectxhr) { $ este o versiune optimizat a lui document.getElementById() setValue e o versiune specializat (pentru elemente INPUT) a metodei setAttribute din JavaScript. Atenie! Avem n Prototype i funcia $F() care ne returneaz valoarea unui INPUT, dar este read-only! Modificarea de valoare se face cu setValue

raspuns=obiectxhr.responseText vector=raspuns.split(',') for (i=0;i<vector.length;i++) { camp=$('desubstituit'+i) camp.setValue(vector[i]) } }

function date(cod)

{ configurari={method:"get",parameters:"CP="+cod,onSuccess:procesareraspuns,onFailure:proceroare} new Ajax.Request("codpostal.php",configurari) } Configurarea XHR e realizat n sintaxa JSON, cu cele 2 evenimente, onSuccess i onFailure, pentru care s-au pregtit funcii.

</script> </head> <body> <h1>Introduceti datele</h1> <form action="destinatie.php" method="post"> <table> <tr> <td>Nume</td> <td><input type=text id=TXnume ></td> </tr> <tr> <td>CodPostal</td>

<td><input type=text id=TXcodpostal onblur="date(this.value)"></td> </tr> <tr> <td>Adresa</td> <td><input type=text id=desubstituit0 size=50></td> </tr> <tr> <td>Oras</td>

<td><input type=text id=desubstituit1></td> </tr> <tr> <td>Judet</td> <td><input type=text id=desubstituit2 ></td> </tr> <tr> <td></td> <td><input type=submit value=Trimite ></td> </tr> </table> </form> <p id=Eroare></p> </body> </html> cmpurile vizate au primit IDuri similare, care s poat fi parcurse printr-un ciclu FOR

Urmtorul exemplu e similar, dar folosete alte faciliti Prototype: o funcie iteratoare, $$, generarea erorii cu Element.insert.

<html> <head> <script type="text/javascript" src="scriptaculous/lib/prototype.js"> </script> Funcia ce se va aplica fiecrui element dintr-un vector (vectorul INPUT-urilor n care inserm valori).

<script>

function substituie(x,pozitie) { x.setValue(vector[pozitie]) } 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)

function proceroare(obiectxhr) { Element.insert('formular',{after:'<div style="color:green"> Mesaj eroare:'+obiectxhr.responseText+'</div>'}) } Acum cmpurile sunt gsite pe baz de CLASS i sunt trecute toate prin funcia de substituire a valorii, cu ajutorul funciei iteratoare each! Important: each transfer 2 valori spre funcia-argument substituie: elementul i poziia sa n vector (vezi argumentele cu care s-a creat substituie())

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

</script> </head>

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

<body> <h1>Introduceti datele</h1> <form action="destinatie.php" method="post" id="formular"> <table> <tr> <td>Nume</td> <td><input type=text id=TXnume ></td> </tr> <tr> <td>CodPostal</td> <td><input type=text id=TXcodpostal onblur="date(this.value)"></td> </tr> <tr> <td>Adresa</td> <td><input class=desubstituit type=text size=50 /></td> </tr> <tr> <td>Oras</td> <td><input class=desubstituit type=text /></td> </tr>

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

</body> </html>

Urmtorul exemplu este similar, dar folosete Ajax.Updater:

Pagina client <html> <head> <script type="text/javascript" src="scriptaculous/lib/prototype.js"> </script>

<script type="text/javascript"> function proceroare(obiectxhr)

{ document.getElementById("Eroare").innerHTML="<font color=red>Eroare"+obiectxhr.responseText+"</font>" } function date(cod) { new Ajax.Updater("t1","sursa.php", { method: "get", parameters: "CodPostal="+cod, onFailure: proceroare }) } Fara onSuccess! Continutul e inserat automat! </script> </head> (onSuccess e necesar doar daca vrem sa se intample si altceva) Marcatorul al carui continut se substituie

<body> <h1>Introduceti datele</h1> <form> <table id=t1> <tr> <td>Nume</td> <td><input type=text id=TXnume ></td> </tr> <tr>

<td>CodPostal</td> <td><input type=text id=TXcodpostal onblur="date(this.value)"></td> </tr> <tr> <td>Adresa</td> <td><input type=text id=TXadresa size=50></td> </tr> <tr> <td>Oras</td> <td><input type=text id=TXoras ></td> </tr> <tr> <td>Judet</td> <td><input type=text id=TXjudet ></td> </tr> <tr> <td></td> <td><input type=submit value=Trimite ></td> </tr> </table> </form> <p id=Eroare></p> </body> </html> Scriptul server (salvat ca sursa.php)

<?php

if ($_GET["CodPostal"]==400451) { $oras='Cluj Napoca'; $judet='Cluj'; $adresa='Aleea Azuga....completati detaliile'; } else { $oras='cod incorect'; $judet='cod incorect'; $adresa='cod incorect'; } $cod=$_GET["CodPostal"]; Serverul rescrie integral coninutul tabelului, cu noile date inserate (parial redundant!)

print "<tr> <td>Nume</td> <td><input type=text id=TXnume ></td> </tr> <tr> <td>CodPostal</td> <td><input type=text id=TXcodpostal value='$cod' onblur='date(this.value)'></td> </tr> <tr>

<td>Adresa</td> <td><input type=text id=TXadresa size=50 value='$adresa'></td> </tr> <tr> <td>Oras</td> <td><input type=text id=TXoras value='$oras'></td> </tr> <tr> <td>Judet</td> <td><input type=text id=TXjudet value='$judet'></td> </tr> <tr> <td></td> <td><input type=submit value=Trimite ></td> </tr>";

?>

Obs: 1. Ajax.Updater nu necesita definirea unei functii de procesare a raspunsului (raspunsul e inserat automat, se poate insa crea o functie de procesare daca se doreste ca raspunsul sa sufere modificari inainte de inserare). 2. Exemplul este ineficient deoarece realizeaza refresh redundant (se reincarca tot continutul tabelului, inclusiv marcatori care exista deja in pagina client!) De ce aceast redundan? Pentru c nu putem substitui doar cteva rnduri de tabel! Ele ar trebui s fie cuprinse ntr-un DIV pe care s-l folosim ca int pentru Updater dar marcatorul TABLE avea ca noduri fiu DIVuri (doar TR!) orice nod-fiu nepermis al lui TABLE este mpins n afara tabelului!

Sarcina: Modificati exemplul pentru a minimiza refreshul redundant! (sa vina de la server doar cele 3 campuri ce se actualizeaza): Variante de soluii: 1. executm de cte un Ajax.Updater pentru fiecare celul (TD) al crei coninut se modific; 2. nu mai ncadrm formularul ntr-un tabel, ci ntr-o structur de DIVuri care s arate ca un tabel (astfel nct s putem defini un DIV convenabil care s substituie doar cmpurile ce sufer modificri) 3. celulele ale cror cmpuri se substituie se grupeaz ntr-o singur celul (ROWSPAN) pe care o folosim ca int (i dm ID) pentru Updater.

Exemplu PeriodicalUpdater

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

<script>

function initializare() { config={method:"get",frequency:1,decay:2} perup=new Ajax.PeriodicalUpdater("scor","sursascoruri.php",config) }

</script>

</head>

<body onload="initializare()">

<div>Steaua <span id="scor"> 0 - 0 </span>Dinamo</div> <input type="button" value="Reporneste updaterul periodic" onclick="perup.start()"/> <input type="button" value="Opreste updaterul periodic" onclick="perup.stop()"/>

</body> </html>

Script server, salvat cu numele sursascoruri.php:

<?php print " 0 - 0 "; ?>

Mod de utilizare: - la ncrcarea paginii ncep updateurile periodice (putei s le urmrii n Firebug). Vor deveni tot mai rare datorit decayului de 2, care dubleaz intervalul dintre dou updateuri.

La un moment dat intrai n scriptul server i scriei 1-0 n loc de 0-0. Dup cteva secunde, noul scor apare n pagin i decayul se reseteaz (cererile devin din nou dese).

Cu cele dou butoane putei opri sau reporni updaterul periodic.

Modificm exemplul pentru a folosi i 2 evenimente: - onSuccess execut operaii suplimentare la FIECARE update; - onComplete se execut doar la oprirea updaterului.

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

<script> function finalizare() { alert('Se opreste updaterul') }

function confirmare() { alert('Tocmai a avut loc un update') }

function initializare() { config={method:"get",frequency:1,decay:2,onSuccess:confirmare,onComplete:finalizare} perup=new Ajax.PeriodicalUpdater("scor","sursascoruri.php",config)

} </script>

</head>

<body onload="initializare()">

<div>Steaua<span id="scor"> 0 - 0 </span>Dinamo</div> <input type="button" value="Reporneste updaterul periodic" onclick="perup.start()"/> <input type="button" value="Opreste updaterul periodic" onclick="perup.stop()"/>

</body> </html>

Lab 4 Comportamente preprogramate in Scriptaculous Clonare spaial, Autocompleter, Slider, InPlaceEditor

Clonarea spaial (de poziie i dimensiuni)

Un instrument important n Prototype este clonarea spaial, necesar n coordonarea poziionrii i dimensionrii unor elemente n funcie de altele.

Scriptaculous, pe lng c ofer funcia de clonare, se i bazeaz pe aceasta n alte funcii, cum ar fi cele legate de Autocompleter (pentru poziionarea listei de sugestii imediat sub textboxul n care se face cutarea).

n general clonarea spaial se folosete n 3 moduri: pentru a plasa un element n locul altuia (care a fost ascuns); pentru a plasa un element n dreptul altuia (lng, dedesubt, deasupra etc.) prin clonare parial (cu un anumit decalaj, limitat la o ax); pentru a dimensiona un element n funcie de altul.

E important de stpnit operaia de clonare, de cunoscut problemele pe care le creeaz browserele i modul n care le putem depi.

De aceea e recomandat ca: paginile s se creeze cu o declaraie <!DOCTYPE html> pe prima linie, ceea ce face browserele s intre n modul standard i s dea rezultate mult mai uniforme (dimensionarea i poziionarea sunt printre operaiile cele mai afectate de diferenele dintre browsere, iar prezena unei declaraii DOCTYPE face s dispar mare parte din diferene); funcia clonePosition s fie folosit pe elemente fr border i padding, altfel apar diverse decalaje ce pot necesita ajustri manuale.

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

<!DOCTYPE html> <html> <head> <style> #d1 {width:200px;height:100px;color:red;border:2px solid red} #d2 {width:100px;height:50px;color:blue;border:2px solid blue} </style> <script src="scriptaculous/lib/prototype.js" type="text/javascript"></script>

<script> function cloneaza() { Element.clonePosition('d2','d1') } </script> </head> <body> <input type="button" value="click aici pt clonare de pozitie" onclick="cloneaza()"/> <div id="d1">Acesta e primul div</div> <div id="d2">Acesta ii va clona pozitia primului</div> Text oarecare </body> </html>

n aceast form de baz, clonePosition nu face dect s cloneze dimensiunile: al doilea DIV se mrete pn la dimensiunile primului (aproximativ, cci 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 trziu de eliminarea manual a acestei abateri). Pentru a clona att dimensiunile ct i poziia, e necesar ca elementul care primete noua poziie s fie scos din fluxul natural al textului, deci s fie definit cu poziionare ABSOLUT sau FIX! Poate fi definit i cu poziionare RELATIV, care se face relativ la poziia curent (deci e util dac vrem s mutm un element sincron cu alt element, dar pstrnd distana relativ dintre ele).

Aplicm poziionare absolut pe DIVul ce va fi manipulat:

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

Avertisment: prin poziionare 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 coninut din zona respectiv. De aceea elementele cu poziionare absolut de obicei sunt elemente ce apar temporar peste coninutul paginii (deci au un background definit, de ex. casete de dialog cu afiare temporar). Uneori sunt iniial ascunse i sunt fcute vizibile din cnd n cnd pentru a afia diverse mesaje sau coninut. n general elementele care se mic mult n pagin se definesc cu poziionare absolut pentru a nu fi influenate de restul paginii.

n urma poziionrii absolute, se vede c butonul de clonare aplic acum i clonare de poziie dar... cu o abatere neplcut.

Aceast abatere e prezent din cauz c browserele impun implicit o bordur pe marginea paginii, de care clonarea poziiei nu ine cont. Soluia pentru evitarea abaterii este una dintre urmtoarele: anularea oricrei margini pentru BODY: <body style=margin:0px> poziionarea relativ a lui BODY: <body style=position:relative>

Acum clonarea ar trebui s funcioneze corect.

Clonarea parial

Mai frecvent dect clonarea total a poziiei, este necesitatea de a poziiona un element n dreptul altuia. Pentru aceasta, clonePosition ne permite s impunem un decalaj suplimentar:

Element.clonePosition('d2','d1',{offsetLeft:200}) ... aplicnd un decalaj al marginii stngi cu o valoare egal cu limea primului DIV, al doilea va fi poziionat la dreapta primului

Element.clonePosition('d2','d1',{offsetTop:100})

...aplicnd un decalaj al marginii de sus cu o valoare egal cu nlimea primului DIV, al doilea va fi poziionat dedesubt

Obs: Pentru deasupra i la stnga, dm valori negative celor 2 decalaje; Dac dorim s poziionm col-n-col, combinm ambele decalaje; Dac trebuie neaprat s avem chenare vizibile i micile lor suprapuneri sunt suprtoare, vom aduga i grosimea chenarelor la decalaj: 204, respectiv 104 n aceste exemple (dublul grosimii lui border); la fel, dac primul DIV are padding, l vom aduga i pe acesta la decalaj (nu i bordura margin, care nu e inclus n dimensiunea elementelor!).

Alte opiuni:

Element.clonePosition('d2','d1',{setTop:false}) ...cloneaz doar poziionarea orizontal, pe vertical pstreaz poziia curent Element.clonePosition('d2','d1',{setLeft:false}) ...cloneaz doar poziionarea vertical, pe orizontal pstreaz poziia curent Element.clonePosition('d2','d1',{setWidth:false}) ...cloneaz totul n afar de lime Element.clonePosition('d2','d1',{setHeight:false}) ...cloneaz totul n afar de nlime

Combinnd aceste opiuni, putem clona fie doar dimensiunile, fie doar poziia, fie diverse combinaii ntre ele limitate la axa X sau axa Y.

Influena chenarelor i bordurilor

S-a vzut la clonarea cu decalaj c n decalaj ar trebui inclus i grosimea chenarelor i paddingului, cnd acestea exist n primul DIV, cci clonarea nu ine cont de ele. n exemplul precedent am sugerat s se calculeze manual dimensiunea total (204), adunnd eventualul chenar sau padding (2*border=4) la dimensiunea declarat (200).

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

Modificai 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, cci trebuie s adunm i grosimea paddingului. Pentru a ne scuti de astfel de calcule, Prototype ne ofer funciile getWidth/getHeight care ne returneaz dimensiunile calculate ale elementelor (incluznd border i padding!)

Modificm funcia 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 clonrii de dimensiune, suntem nevoii s facem ajustri mai detaliate, cci clonePosition nu ne ofer opiune de aplicare a unei abateri de dimensiune i trebuie s manipulm direct CSSul.

Adugai 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 funciei de clonare (a doua parte a funciei):

function cloneaza() { //plasarea alaturata a celor 2 divuri latime=Element.getWidth('d1') Element.clonePosition('d2','d1',{offsetLeft:latime})

//citirea inaltimii calculate a primului div (cu padding si border) inaltime1=Element.getHeight('d1') //citirea grosimii chenarului chenar=Element.getStyle('d2','border-top-width') //citirea grosimii paddingului pad=Element.getStyle('d2','padding-top') //calcularea inaltimii de declarat pt div 2, ca sa ajunga la inaltimea calculata a divului 1 //functia parseInt e necesara caci functiile de mai sus returneaza valorile ca string, nu ca numere inaltime2=parseInt(inaltime1)-2*parseInt(chenar)-2*parseInt(pad) //declararea noii inaltimi (care adunata cu bordurile va da inaltimea calculata dorita) Element.setStyle('d2',{height:inaltime2+'px'})

Autocompleter

Autocompleter se folosete de obicei n corelaie cu un textbox, cnd, n timpul completrii acestuia, utilizatorul primete sugestii privind datele pe care s le tasteze. De obicei mecanismul e folosit la motoare de cutare. Sugestiile sunt afiate 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 cutrile care s-au mai fcut de ctre ali utilizatori) sau s fie generate n JavaScript (ex: un cookie cu cutrile pe care le-a mai fcut utilizatorul curent).

Pasul1. Pregtim cmpul text i zona de afiare a sugestiilor.

<!DOCTYPE html> <html> <head> <style>

div#sugestii {width:250px;border:1px solid red} div#sugestii ul {border:1px solid blue} div#sugestii ul li.selected {background-color:#ffb;} div#sugestii ul li {border:1px solid green;cursor:pointer;}

</style> </head> <body> Caseta de cautare:<br/> <input type="text" id="tbox"/> <div id="sugestii"> <ul> <li class="selected">prima sugestie</li> <li>a doua sugestie</li> <li>a treia sugestie</li> </ul> </div> </body> </html>

Am creat un textbox cu IDul tbox (n mod normal e nevoie i de un buton care s declaneze cutarea pe server, dar l vom neglija cci nu dorim s implementm cutarea efectiv ci doar generarea sugestiilor). Imediat dup el am creat un DIV rezervat pentru afiarea sugestiilor, cu IDul sugestii. n DIVul de sugestii am inserat o list neordonat. Important: Autocompleterul din Scriptaculous este preprogramat astfel nct s atepte sosirea de la server a unei liste UL (chiar dac serverul nu gsete nici o sugestie, el tot va trebui s returneze un marcator UL, fie i unul gol!) Din acest motiv, n aceast faz ne formatm sugestiile sub forma unei liste-lociitor (ca i cum aceasta ar fi venit deja de la server), apoi vom terge lista pstrnd doar formatrile. Unul din elementele listei are class=selected. i acest lucru este impus de Scriptaculous! Scriptaculous va genera automat acest atribut pe elementul deasupra cruia se afl mouseul deci trebuie s-i pregtim o formatare distinct de restul listei.

Am construit cteva stiluri, unele doar provizoriu: o Am pus chenare (border) la DIVul sugestii si elementele sale pentru a vedea mai clar ce modificari de poziionare trebuie s le aducem; Am modificat culoarea elementului presupus activ (cu class=select) din list; Am modificat tipul cursorului la trecerea mouseului peste elementele listei.

o o

Verificai cum arat n acest moment pagina. n ce privete poziionarea sugestiilor imediat sub textbox, avem dou variante de lucru: S ne bazm c Autocompleter va calcula corect poziionarea (prin clonarea de care am discutat nainte); S definim noi poziionarea (grupnd textboxul i sugestiile n acelai DIV, s le putem manevra ca un ntreg) i s dezactivm poziionarea automat oferit de Autocompleter.

Pasul2. Formatm lista de sugestii pentru a putea fi afiat sub forma unui meniu.

n acest moment lista de sugestii trebuie s sufere cteva formatri: s dispar bulinele de la elemente (list-style-type:none); s dispar spaiul liber din jurul listei i din faa elementelor listei (acest aspect este mai problematic, cci e tratat diferit de browsere la unele acest spaiu liber e controlat cu margin, la altele cu padding, deci va trebui s punem ambele proprieti la zero); cnd totul e poziionat cum trebuie, eliminm chenarele.

Toate aceste modificri se aplic n momentul n care modificm stilul CSS al listei, astfel:

div#sugestii ul { list-style-type:none; margin:0px;

padding:0px; }

i eliminm chenarul de la elementele listei:

div#sugestii ul li { cursor:pointer}

Chenarul DIVului l lsm vizibil.

Pas3. Crem scriptul server provizoriu

n aceast faz crem un script foarte simplu, care s returneze o list oarecare, pentru a ne convinge c mecanismul funcioneaz. 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>'; ?>

Salvm fiierul cu numele sugestii.php. Observai 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 ncrcarea paginii. Alternativ, ar

putea fi generat printr-un eveniment ONFOCUS sau ONCLICK (cnd se intr cu mouseul n textbox) sau prin alte evenimente.

Adugai mai nti liniile de apel al funciilor 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 asociai 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 afiarea sugestiilor); sugestii.php (scriptul ce trimite sugestiile); {} (obiect cu argumente opionale, n cazul de fa fr opiuni).

n plus, se poate terge lista UL scris deja (era provizorie, doar ct s-i putem face stiluri; de aici ncolo, lista va fi inserat automat din sugestiile venite de la server!)

Corpul paginii va rmne: <body onload="initializare()"> Caseta de cautare:<br/> <input type="text" id="tbox"/> <div id="sugestii"></div> </body>

Pas5. Poziionarea cu bug

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

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

O alt metod de depire a bugului este s dezactivm poziionarea automat oferit de Scriptaculous, caz n care va trebui s facem manual poziionarea (s ne asigurm c sugestiile i textboxul sunt grupate n acelai DIV, unul dup altul sau poziionat cu CSS). Dezactivarea clonrii de poziie are loc dac DIVul de sugestii primete poziionare relativ (nu o facei n acest exemplu, aplicai cealalt msur):

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

Pas6. Modificarea scriptului server pentru a face cutare.

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

n prima faz, iniializm 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 asigurm c textboxul trimite date la server, adugndu-i un atribut NAME:

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

Apoi, s capturm aceast valoare la server (nu avem nevoie de Submit, Autocompleterul se ocup de trimiterea asincron a coninutului din textbox, dup fiecare tast apsat sau cu o frecven pe care o putem modifica prin opiunile suplimentare de la instanierea Autocompleter!):

Prelum la server coninutul textboxului: $litere=$_POST['litere'];

Construim o expresie regulat care s verifice dac un string ncepe cu literele sosite prin POST (caseinsensitive): $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 interogri cu LIKE. Asta ar nsemna s programai i cutarea efectiv. Asta presupune: ncadrarea textboxului ntr-un formular, care va avea un buton Search (de tip submit) i ca destinaie un script PHP care face dou lucruri: o o cutarea efectiv dorit de utilizator; memorarea cuvntului cutat ntr-un tabel dedicat pentru cuvinte cutate; apoi, scriptul sugestii.php va fi modificat s scoat sugestiile din acest tabel cu interogri LIKE

V rmne acest mecanism ca sarcin de lucru.

Eventual, n tabelul dedicat cutrilor, se poate stoca i numrul de cutri fcute cu fiecare cuvnt, pentru a se putea scoate doar sugestiile mai populare primele cteva, cum face Google, cci 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 nite informaii suplimentare care s-l ajute pe utilizator n selectarea sugestiei dorite, FR CA informaiile suplimentare s fie inserate n textbox la momentul deciziei.

Dac pur i simplu adugm text suplimentar n elementele listei...

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

...acesta va deveni parte din sugestie i va fi inserat n textbox, cnd 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 afiat n lista de sugestii, dar nu va fi preluat la selectarea unei sugestii. Evident, i acele informaii suplimentare ar trebui preluate dintr-o surs de date i nu concatenate n acest fel.

Opiuni suplimentare:

1.Dac estimm c va dura mai mult cutarea sugestiilor, trebuie s oferim i un indicator grafic (un gif animat, gen clepsidr) care s sugereze o stare de ateptare (cu indicator).

2.Dac vrem ca schimbul de date s fie mai rar, putem preciza dup cte caractere tastate s se iniieze procesul Autocompleter (cu minChar) sau la cte secunde (cu frequency). 3. Dac vrem s trimitem i alte date dect literele tastate, o putem face cu parameters. 4. Dac vrem ca n acelai 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.

Downloadai un gif animat sugestiv (cutai pe Google dup spinner.gif). Salvai imaginea pe server cu numele animatie.gif.

Inserai ntre textbox i DIVul cu sugestii imaginea. Facei-o invizibil i dai-i un ID:

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

Apoi modificai linia de creare a Autocompleterului, cu opiunile suplimentare indicator (IDul imaginii) i minChars (dup cte caractere s apar sugestiile):

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

O opiune suplimentar important este i parameters permite ca Autocompleter s trimit la server i ALTE date dect literele din textbox. Exemplu:

new Ajax.Autocompleter("tbox", "sugestii", "sugestii.php", {indicator:"asteapta",minChars:2, parameters:a=100&b=200})'

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

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

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

La tastarea n textbox a caracterului ; Autocompleterul se reseteaz i trimite la server doar literele ce urmeaz dup ;. Aceasta permite selectarea mai multor sugestii n acelai textbox, separate prin delimitatorul ales.

Autocompleter mai ofer i 3 evenimente crora li se pot asocia funcii personalizate:

callback funcia se va executa n locul comunicrii asincrone (deci dac o folosim va trebui s implementm explicit n aceast funcie comunicarea asincron cu Ajax.Request); funcia primete obligatoriu 2 argumente: numele textboxului i datele de trimis la server; updateElement funcia se va executa la selectarea unei sugestii i va substitui mecanismul automat de inserare a elementului selectat n textbox; funcia primete obligatoriu ca argument elementul selectat; afterUpdateElement funcia se va executa la selectarea unei sugestii, da NU va substitui mecanismul de inserare (ci l suplimenteaz); funcia primete obligatoriu 2 argumente: textboxul i elementul selectat.

Adugai funcia:

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 afiare de mesaj. Pentru a-i vedea efectul, adugai evenimentul updateElement la opiunile suplimentare:

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

Aadar, printr-o astfel de funcie putem realiza operaii suplimentare la momentul selectrii unei sugestii, dar are dezavantajul c trebuie s programm manual inserarea sugestiei n textbox. n schimb dac programm o funcie pentru evenimentul afterUpdateElement putem pstra mecanismul implicit de inserare (cu diferena c funcia acestui eveniment trebuie s aib 2 argumente textboxul i elementul selectat).

La fel se folosete i evenimentul callback, dar acesta substituie comunicarea asincron:

Adugm n pagin funcia:

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

Funcia afieaz o alert la momentul la care AR TREBUI s se realizeze comunicarea asincron (dup tastarea a 2 caractere). Deci dac personalizm o astfel de funcie, trebuie s includem n ea i codul comunicrii asincrone, precum i inserarea listei de rezultate n DIVul de sugestii. Altfel spus, folosirea funciei callback ne oblig s programm manual aproape tot mecanismul Autocompleter.

Autocompleter local E posibil ca lista de sugestii s nu vin de la server, ci s fie generat la nivelul clientului. n acest caz, funcia de iniializare poate arta astfel:

function initializare() { lista=['Ana','Andrei','Bebe','Bogdan'] new Autocompleter.Local("tbox", "sugestii", lista,{}) }

Constructorul nu se mai numete Ajax.Autocompleter, ci Autocompleter.Local i primete 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 ci dect 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. cutrile fcute 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 cutat el nainte, nu i cele cutate de ali utilizatori adesea se practic o combinaie ntre cele 2: Google ne ofer i o list de cutri fcute de noi (din cookie), dar i una fcut de restul lumii).

Pentru aceasta adugm i butonul care s declaneze cutarea:

<input type="button" value="Cautare" onclick="memoreaza()"/> Nu i vom programa cutarea efectiv pe server, dar avem nevoie de el pentru a-i programa n JavaScript crearea cookieului cu cutarea curent:

function memoreaza() {

//aici ar veni codul ce comunica cu scriptul server responsabil cu cautarea, pe baza de Ajax.Request //dar in acest exemplu implementam doar memorarea cautarilor facute in cookie:

//pas1.dam cookieurilor nume de forma cautare1,cautare2 etc. ca sa nu se suprascrie //pentru asta ne bazam pe variabila nrcookie cu care vom numara cookieurile existente //(va trebui sa o si initializam, dar nu aici, ci la evenimentul ONLOAD!) nrcookie++ numecookie='cautare'+nrcookie //dam valoarea cookieului, preluata din textboxul cu cautarea facuta valcookie='='+$F('tbox') //fixam data de expirare setaricookie="; expires=Thu, 2 Aug 2011 20:47:11 UTC" //concatenam componentele cookieului si le memoram pe disc document.cookie=numecookie+valcookie+setaricookie }

Din comentarii se vede c am folosit variabila nrcookie, care nu exist. n ea intenionm s numrm i s numerotm cookieurile, de aceea, la ncrcarea paginii, primul lucru care trebuie fcut este s vedem cte cookieuri exist (dac nu exist nici unul, se va iniializa cu valoarea 1)

function initializare() { //extragem cookieurile existente ntr-un vector pe disc ele sunt fiiere text cu valori delimitate de ; toatecookie=document.cookie.split(';') //aflm cte cookieuri exist dac nu e nici unul, lungimea implicit va fi 1, deci nu e necesar un test nrcookie=toatecookie.length

//trecem vectorul de cookies printr-un iterator, pentru a-i extrage valorile lista=toatecookie.collect(extrage) new Autocompleter.Local("tbox", "sugestii", lista,{}) }

Urmtoarea funcie este cea care asigur extragerea valorilor (apelat de funcia iteratoare collect):

function extrage(elem) { return elem.split('=')[1] }

Funcia primee de la iterator fiecare element al vectorului, i, bazndu-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 (mnerul care se trage de-a lungul inei). Pentru a simplifica poziionarea lor, ideal este ca: ina s conin mnerul; fiecare s aib lime i nlime convenabil; fiecare s aib un fundal sugestiv n acest exemplu le vom colora fundalul ca s creeze impresia de bar i mner dar n practic li se pune pe background cte o imagine sugestiv (PNG cu background transparent) folosind proprietatea background: url(..).

<!DOCTYPE html> <html>

<head> <style> #sina {background:red;width:200px;height:10px} #maner {background:blue;width:10px;height:10px} </style> <script src="scriptaculous/lib/prototype.js" type="text/javascript"></script> <script src="scriptaculous/src/scriptaculous.js" type="text/javascript"></script> <script> function initializeaza() { new Control.Slider("maner","sina",{}) } </script> </head> <body onload="initializeaza()"> <div id="sina"> <div id="maner"></div> </div> </body> </html>

Observai dimensiunile convenabil setate lungime mare pentru in, cu nlime mic,respectiv un ptrel mic, egal cu nlimea inei, pentru mner. 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 influeneaz alte elemente din pagin. n cazul cel mai simplu, gama de valori va fi pur i simplu afiat ntr-un textbox:

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

Pentru a lega Sliderul de textbox, trebuie s definim o funcie pentru evenimentul onSlide, funcie ce primete obligatoriu valoarea curent a Sliderului pe care noi trebuie s o transferm 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 opiunile range i values); afiarea valorii n textbox se face doar cnd tragem mnerul (drag); ar trebui s se fac actualizarea i cnd dm un simplu click undeva pe in (pentru aceasta avem evenimentul onChange).

Modificai sliderul:

function initializeaza() {

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

Acum valorile vor fi n intervalul 10-50 i e posibil i parcurgerea Sliderului cu clickuri. Obs:Alturi de range putem folosi i values:[10,20,30,40,50] care va face ca valorile sliderului s fie discrete (mnerul va sri din 10 n 10, nu va putea fi poziionat oriunde n interval).

Mai departe, adugm consecine noi n pagin: un paragraf care s i schimbe fontul n funcie de slider; un paragraf care s i schimbe poziia vertical n funcie de slider.

Adugm 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 modificm funcia de legtur cu Sliderul, adugnd operaii de ajustare a mrimii de font (pt primul paragraf) i de ajustare a poziiei verticale (pentru al doilea) n funcie de valoarea curent:

function afiseaza(valoare) {

$('valori').setValue(valoare) Element.setStyle('tinta1',{fontSize:valoare+'px'}) Element.setStyle('tinta2',{position:'relative',top:valoare+'px'}) }

Observai: cum este concatenat valoarea Sliderului la proprietile CSS; faptul c numele proprietilor CSS e puin diferit: nu am folosit font-size, ci fontSize (deoarece e acoladele acelea nu sunt cod CSS, ci cod JavaScript, iar n JavaScript toate proprietile cu cratim n nume sufer aceast mic conversie); faptul c att dimensiunea ct i poziia sufer modificri n intervalul 10-50.

Dac dorim ca modificarea aplicat n pagin s nu fie EXACT modificarea Sliderului, ci o valoare proporional, nmulim valoarea cu proporia 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, poziia 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 funcii:

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

- atam textboxului o funcie care s se apeleze la apsarea tasteri Enter

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

- definim funcia respectiv, n care testm dac tasta apsat este Enter (codul 13):

function seteaza(v,e) { eveniment=e||window.event if (eveniment.keyCode==13) sld.setValue(v) }

Observaii: onkeypress este un eveniment ce se apeleaz indiferent ce tast apsm; n consecin, trebuie s-i dm un al doilea argument, event, prin care se va putea verifica despre ce tast e vorba; n interiorul funciei, nu e sigur c argumentul event ajunge! Internet Explorer nu recunoate acel argument, n schimb ofer obiectul window.event cu acelai 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 verificm proprietatea keyCode a evenimentului (Enter are codul 13), apoi cu setValue aplicm o setare de valoare Sliderului.

Codurile tuturor tastelor le gsii aici: http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-CodesKey-Codes.aspx

Tot acest mecanism cu tasta Enter poate fi ocolit: dac folosim evenimentul onblur (dar atunci mutarea mnerului se va face la apsarea lui Tab)

dac punem alturi i un buton la apsarea cruia s mutm mnerul.

Totui 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 crem i un buton.

n plus, am folosit acest exemplu ca pretext pentru a arta cum se captureaz evenimentul de apsare a unei taste anume.

n sfrit, putem desena Sliderul i vertical dac: punem axis:vertical in opiunile suplimentare (inainte de range); redesenm DIVurile astfel nct ina s fie nalt i nu lat.

InPlaceEditor Acest mecanism substituie un text din pagin cu un mic formular la apsarea 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 coninut (cu innerHTML sau, dac folosim Prototype, cu Element.update). ns o astfel de implementare ar avea dezavantajul: coninutul astfel inserat va fi vizibil pn la primul Refresh de pagin; putem s-l facem persistent memorndu-l ntr-un cookie (cum am fcut la Autocompleter.Local), dar tot rmne dezavantajul c singurul utilizator care va putea vedea acel coninut 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 ali.

Ori scopul principal al lui InPlaceEditor e s permit inserarea de coninut permanent i pentru toi utilizatorii n pagin, de exemplu comentariul pe un blog sau pe un site. Asta nseamn c obligatoriu trebuie implicat serverul.

<!DOCTYPE html>

<html> <head> <script src="scriptaculous/lib/prototype.js" type="text/javascript"></script> <script src="scriptaculous/src/scriptaculous.js" type="text/javascript"></script> <script> function initializare() { 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> E preferabil ca toate editoarele InPlace din pagin s fie iniializate la ncrcarea paginii (desigur, ele pot fi iniializate i la primul click pe comentariu). Doar IDul i scriptul server sunt obligatorii. Acoladele conin un obiect JSON cu opiuni suplimentare (aici dimensiunile casetei)

Scriptul server (sursaeditor.php):

<?php print $_POST["value"];

?>

Obs: n mod convenional, 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 dect s recepioneze coninutul 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 afiat tot timpul i pentru toi viitorii utilizatori; opional: postprocesarea comentariului, prin aplicarea unor concatenri (formatri, coninut suplimentar) la textul comentariului nainte ca acesta s fie returnat la client; postprocesarea nu e obligatoriu s aib loc pe server (dect dac necesit informaii suplimentare din baza de date sau din sesiune), dac e vorba doar de formatri i mesaje suplimentare poate fi gestionat dinamic i n JavaScript, cu ajutorul funciilor handler pentru evenimentele InPlaceEditor.

Ca opiuni suplimentare, avem posibilitatea s personalizm 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 personalizm stilurile aplicate asupra formularului inserat i asupra DIVului n perioada sa de ateptare (Saving). n special al doilea este important cci ne permite s afim un indicator de ateptare (spre deosebire de Autocompleter, nu l are implementat ca opiune dedicat).

Stilurile trebuie s aib urmtoarele nume standard (sunt 2 CLASSuri pe care InPlaceEditor le genereaz automat, prin convenie):

<style> .inplaceeditor-saving {background:url('spinner.gif') no-repeat;width:100px;height:100px} .inplaceeditor-form {background:green} </style>

Primul corespunde perioadei de ateptare (i alocm un GIF animat), al doilea corespunde formularului (evident, poate fi personalizat pentru fiecare component a formularului, cunoscnd faptul c acesta conine o caset textbox sau textarea, i 2 butoane sau 2 linkuri). Mai departe, n definirea mecanismului, avem grij ca textul de ateptare 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 alocm funcii ctorva evenimente care au loc n timpul procesului:

function initializare() { new Ajax.InPlaceEditor("edt","sursaeditor.php", {rows:10,cols:10,onEnterEditMode:f1,onLeaveEditMode:f2,ajaxOptions:{onSuccess:f3}}) } function f1() { alert('Se intra in modul editare') } function f2()

{ alert('Se iese din modul editare') } function f3() { alert('Transfer realizat cu succes') }

Obs: onEnterEditMode i onLeaveEditMode sunt evenimente de comutare a strii (trecerea de la text la formular i invers) specifice lui InPlaceEditor; pentru a testa succesul i eecul schimbului cu serverul, evenimentele customizate nu funcioneaz datorit unui bug o soluia este s folosim opiunea suplimentar ajaxOptions, ce ne permite s transferm configurri spre mecanismul de baz Ajax.Request, inclusiv evenimentele suportate de acesta - onSuccess, onFailure etc.

i aici putem personaliza o funcie callback, dac dorim s reprogramm ntregul mecanism.

Avem i posibilitatea de a realiza comutarea editorului pe alte ci dect prin click pe paragraful original:

- introducei dup comentariu butonul boldat mai jos:

<div style="border:2px solid red; width:100px" id="edt"> Tastati aici comentariul dumneavoastra </div> <input id="but" type="button" value="apasa aici pt a edita comentariul"/>

- reconfigurati editorul, indicnd 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 funcionalitatea, avem funcia dispose() ce se poate apela oriunde n pagin, dac dm un nume obiectului InPlaceEditor:

ed=new Ajax.InPlaceEditor("edt","sursaeditor.php",{rows:10,cols:10 }) apoi, undeva n pagin crem un buton sau alt mecanism care s apeleze oprirea. <input type="button" value="opreste editorul" onclick="ed.dispose()"/>

Sarcina de lucru:

Modificati mecanismul astfel incat pagina sa afiseze 2 articole de blog, fiecare cu cate 1 comentariu sub el (luate dintr-o baza de date cu articole si comentarii). Toate comentariile sa fie modificabile cu InPlaceEditor care, pe server, substituie in baza de date comentariul cu cel tastat. Sub comentarii sa mai fie cate un DIV pentru adaugare de comentariu nou (tot cu InPlaceEditor, dar de data aceasta pe server va avea loc adaugare de comentariu in baza de date, nu substituire!).

Sugestie privind baza de date:

Un tabel cu articole: IDarticol, Textarticol Un tabel cu comentarii: IDcomentariu, IDarticol, Textcomentariu

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

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

Creai n Oxygen un document XML nou.

n fereastra de creare se pot observa jos dou butoane: butonul Customize ne las s precizm un vocabular, DAC dorim ca documentul nostru s se alinieze (s se valideze) fa de regulile impuse de un vocabular;

deocamdat nu crem vocabulare, aa c apsm Create.

Introducei urmtorul 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 tastai, vi se atrage atenia c acesta nu e un document XML bine format. Acest mesaj apare automat n partea de jos a documentului sau, dac apsai butonul de testare a bunei formri, n zona de output.

Obs: Atunci cnd zona de output se ncarc prea tare cu mesaje, o putei cura. De asemenea, putei reveni oricnd la mesajele precedente.

Un exemplu de subiect pentru proba practic (sau pt scris) ar fi s identificai care sunt regulile de bun formare pe care le ncalc documentul? n exemplul de fa, rspunsul ar fi: documentul nu are rdcin 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 conin spaiul, 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 creai urmtorul document bine format:

n text voi folosi majuscule de cte ori m refer la marcatori i atributele lor, ca s fie mai uor de citit textul.

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

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

n timpul tastrii, observai c Oxygen are o serie de faciliti de tastare prin care ncearc s v mpiedice de la a crea elemente prost formate: de cte ori scriei un marcator v adaug automat nchiderea lui, de cte ori modificai un marcator v modific i perechea lui, de cte ori deschidei 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.

Dai un click pe primul produs. Vei remarca o serie de aspecte printre care: generarea automat a unei ci Xpath absolute care ar putea returna elementul selectat; sublinierea cu gri a elementului selectat este important pentru cile RELATIVE, care vor fi calculate de la nodul subliniat; n dreapta se mai poate vedea i zona de gestiune a atributelor elementului selectat

Alt aspect de interfa important e modul de vizualizare a documentului: modul Text este ceea ce se vede n acest moment; modul Author este varianta formatat, cu condiia s se fi creat stiluri CSS pentru marcatori (nu e cazul deocamdat, vom primi o eroare CSS dac ncercm s intrm pe Author); modul Grid este un alt mod de afiare a arborelui DOM, care e util pentru c ne arat exact ci copii are fiecare nod (n timp ce n modul Text acest lucru nu e ntotdeauna evident).

De exemplu, la ntrebarea ci copii are nodul COMANDA?, ai putea fi tentai s rspundei cu 6: 1 comentariu, 3 elemente PRODUS, 1 element PRETTOTAL si 1 nod text.

n realitate numrul de copii este 11, pentru c mai exist nc 5 Enteruri considerate noduri text (nsoite uneori de spaii sau Taburi pt indentare!):

<?xml version="1.0" encoding="UTF-8"?> <comanda> <!-- acesta e un comentariu --> <produs denumire="Televizor" id="p1" pret="100"/> <produs id="p2" pret="200">Calculator</produs>

<produs> <id>p3</id> <pret>300</pret> <denumire>Ipod</denumire> </produs> <prettotal>600</prettotal> Aceasta este o comanda de produse </comanda>

De ce nu sunt considerate noduri text i celelalte 5 Enteruri? Primele trei sunt noduri text, dar sunt copiii celui de-al treilea PRODUS, nu al lui COMANDA; Ultimele dou sunt ale lui COMANDA, dar fac parte din acelai nod text ca i propoziia pe care o ncadreaz. Deci ultimul nod text este de fapt {ENTER}Aceasta este o comanda de produse{ENTER}

Prezena tuturor Enterurilor va afecta rezultatele cilor Xpath bazate pe poziie. n programare se evit n general prezena Enterurilor prin cteva metode: dac documentul XML e luat dintr-un fiier sau string, avem grij ca n acel fiier/string s scriem totul pe un singur rnd, fr Enteruri (dezavantaj: dificultate de citire i editare!) se ncarc fiierul/stringul cu Enteruri cu tot i se creeaz o funcie de curare recursiv a nodurilor invizibile (Enteruri, Taburi etc.); dac se construiete documentul XML direct prin program, nod cu nod, aceast problem nu mai apare cci funciile de lipire a nodurilor la arborele DOM (appendChild, insertChild etc.) genereaz un XML curat, fr noduri invizibile!

n Oxygen vom pstra Enterurile pentru citirea mai uoar a exemplelor. Oricum, am amintit deja c vizualizarea n mod Grid ne evideniaz mai bine aceste noduri invizibile.

Afiarea n Grid se face pe nivele, cu posibilitatea de a detalia sau restrnge coninutul fiecrui element, folosind sgeile. Prntre elemente se pot vedea nodurile invizibile, cu numele i valoarea (rnd gol).

La ultimul nod text se poate vedea c a fost reunit cu un Enter (rnd gol) i un Tab n fa, iar n spate cu un Enter (comparativ, valoarea 600 de deasupra, nu are nici un rnd gol sau spaiere).

Obs: n versiunea curent Oxygen are un bug: dac dai click pe ultimul PRODUS din Grid, nu vei vedea nodurile invizibile dei are i acesta 3. O metod mai sigur de a vedea corect arborele DOM complet este opiunea Tools- Tree Editor:

La vizualizarea cu Tree Editor, pentru a vedea toate nodurile, mai trebuie s activai butoanele care fac vizibile atributele, nodurile invizibile (white spaces) i comentariile. Se pot vedea nodurile invizibile (casetele goale cu un T n fa).

Probleme legate de arborele DOM

n concluzie, arborele DOM al acestui exemplu arat astfel:

Documentul (nodul document)

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

comanda (elementul document)

Nod text invizibil

Nod comentariu

Nod text invizibil

produs (nod element) produs

Nod text invizibil

produs (nod element) produs

Nod text invizibil

produs (nod element) produs

Nod text invizibil

prettotal (nod element) produs

Aceasta este o comanda de produse (nod text vizibil+invizibil)

Id=p1 pret=100 denumire= Televizor

Id=p2 pret=200

Calculator (nod text vizibil)

Nod text invizibil

id (nod element) produs

Nod text invizibil

pret (nod element) produs

Nod text invizibil

denumire (nod element) produs

Nod text invizibil

600 (nod text vizibil)

p3 (nod text vizibil)

300 (nod text vizibil)

Ipod (nod text vizibil)

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

Exist cteva dileme cu care se confrunt nceptorii (datorit unor confuzii legate de terminologie, confuzii generate de literatura de specialitate):

Confuzia 1: Rdcina

Termenul Rdcin este folosit cu dou sensuri: pentru a se referi la rdcina real a arborelui DOM (documentul n ansamblu, termenul standard fiind the document node, nodul document); pentru a se referi la marcatorul/elementul rdcin (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, aa c autorii folosesc n mod liber cuvntul rdcin pentru a se referi ba la unul, ba la cellalt. Eu n general folosesc termenul rdcin pentru a m referi la marcatorul care conine toi ceilali marcatori (elementul document), considernd c toat lumea tie c acesta trebuie obligatoriu precedat de declaraia <?xml...?>.

Confuzia v poate afecta cel mai des atunci cnd creai un arbore DOM prin programare, nod cu nod. Exist tentaia de a crea toate elementele, a le lipi ntre ele, dar a uita c i elementul rdcin 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 rdcinii la document, dup ce n rdcin s-a introdus tot ce trebuie)

Confuzia 2: Sunt atributele noduri?

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

Totui, n general atributele sunt tratate diferit de noduri. De exemplu: cutrile Xpath generice dup funcia node() vor returna ORICE nod (de orice tip: text, elemente, comentarii etc.) dar nu i atribute (cutarea generic de atribute se face cu @*); nodurile au o ordine relevant (le accesm de obicei pe baz de poziie) n timp ce atributele de obicei se acceseaz pe baz de nume (ordinea lor e irelevant 2 marcatori care au aceleai atribute n ordini diferite sunt considerai echivaleni); totui, sunt i softuri care permit parcurgerea atributelor pe baz de poziie (n ordinea n care au fost scrise) dar nu e bine s ne bazm pe acea ordine; atributele vor fi ignorate de funciile care caut pe baza relaiilor 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 gndii la atribute ca la nite proprieti 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 urmtorul nivel (4), ci ntre nivele (3 i 4) sugernd astfel faptul c nu sunt chiar noduri-copil, ci au un statut aparte.

Confuzia 3: Numele i valoarea nodurilor

n sfrit, o confuzie care afecteaz mai mult la nivel de programare este semnificaia 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 puin evident. De exemplu care e numele i valoarea n cazul <produs>Televizor</produs> ???

Rspunsul intuitiv ar fi c produs este numele i televizor valoarea. n realitate, exist dou viziuni:

A. Standardul DOM specific astfel: n acest exemplu avem 2 noduri, unul de tip element, cu un fiu de tip text. FIECARE din acestea are un nume i o valoare! Nodul element are numele produs i valoarea null! Nodul text are numele standard #text i valoarea Televizor!

Orice programator care lucreaz cu standardul DOM va trebui s in de acest fapt (stringul "Televizor" nu e valoarea direct produsului, ci valoarea primului copil al nodului PRODUS). Pentru accesarea stringului Televizor, prin funcii DOM va fi necesar o construcie 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 (att de programare, ct i XML Schema, XSLT sau Xpath) ocolesc sau extind standardul DOM cu nite funcii 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 conine un pointer spre nodul PRODUS, ci chiar coninutul textual al acestuia!) $marcator=$x->getName (capturarea marcatorului)

Comparai cele dou sintaxe. Le vom exemplifica ntr-un seminar viitor, cnd vom construi un XML prin programare. Varianta A ar trebui s funcioneze n orice limbaj (fiind standardizat), varianta B nu neaprat (dar fiind foarte popular, e disponibil sub forma a diverse biblioteci pentru majoritatea limbajelor).

n programare, trebuie s fii ateni care din variante este suportat de limbajul cu care lucrai. Multe limbaje le suport pe AMBELE: n PHP, dac am creat documentul cu clasa DOMDocument, trebuie s respectm standardul (varianta A), dac l crem 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 cci Internet Explorer refuz s o implementeze (poate fi testat n Firefox dar i acolo are unele buguri).

Ca mod de gndire, Xpath e mai aproape de versiunea B (consider c valoarea unui element e chiar coninutul su).

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 dect noduri din arborele DOM, de exemplu locaii (poziii din documente), fragmente (orice poriune dintre dou poziii, de exempu buci de taguri, buci de atribute) sau colecii de fragmente;

XML Signature i XML Encryption pentru semnarea digital sau criptarea unor noduri din document.

Ca i SQL, Xpath suport i anumite calcule aplicate asupra rezultatelor, iar de la versiunea Xpath 2.0, suport inclusiv: interogri condiionale if A then B else C, unde A,B,C sunt interogri Xpath (dac interogarea A gsete soluii, atunci execut interogarea B, altfel pe C), iterative: for $i in X return Y, unde X, Y sunt interogri Xpath ,iar X e o interogare care a returnat mai multe rezultate (pentru fiecare rezultat al interogrii X, execut interogarea Y); operaii pe mulimi (intersecie, reuniune, diferen ntre rezultatele unor interogri ce returneaz mai multe soluii); expresii regulate.

Xpath 2.0 e nc slab implementat. n JavaScript i PHP deocamdat se pot rula doar interogri Xpath 1.0.

Spre deosebire de SQL, Xpath poate doar citi informaii, nu i modifica/insera/terge. Pentru acestea se apeleaz la funciile standard DOM (dac modificrile se fac direct pe documentul iniial) sau la limbajele de transformare XSLT/Xquery (dac dorim s generm un nou document n urma modificrilor).

Interogrile Xpath se execut n caseta dedicat din stnga sus, unde se poate selecta i versiunea Xpath n care dorim s lucrm.

Obs: Xpath 2.0 SA reprezint interogri Schema Aware, pentru documente cu vocabular de exemplu dac prin interogare cutm 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. Interogrile de tip SA sunt cele care verific dac are sau nu sens interogarea, n funcie de regulile impuse de vocabularul documentului.

n timpul tastrii unei inteorgri, apar liste de sugestii i un Help contextual, foarte util n familiarizarea cu cuvintele cheie din XPath

Rezultatele se afieaz n panoul de output:

n mod implicit, interogrile sunt ci absolute, ncep de la rdcin. Dac dorim ci relative, trebuie s alegem nodul curent, printr-un click pe marcatorul dorit, care va fi subliniat cu gri.

Executarea unei interogri nu d rezultatul ca un simplu string, ci indic i poziia din document la care sa gsit rezultatul: /comanda/produs/id ....caut elementele ID, din PRODUS, din COMANDA i afieaz 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.

Ci absolute simple (fr axe, fr condiii)

/comanda/produs/@id ...caut atributul ID al elementelor PRODUS din COMANDA (se vor gsit 2 soluii, cu valorile p1 i p2)

/comanda/produs ....toate elementele PRODUS din COMANDA (3 soluii)

/comanda ...toate elementele COMANDA (returneaz elementul rdcin)

/ ... returneaz nodul document

Ci generice /comanda/*

...toate elementele din COMANDA (4 soluii)

/comanda/text() ....toate nodurile text din COMANDA (6 soluii, din care 5 sunt nodurile invizibile)

/comanda/node() ....toate nodurile din COMANDA, indiferent de tip (11 soluii din care 6 text, 4 element, 1 comentariu)

/comanda/comment() ....toate comentariile din COMANDA (1 soluie)

/comanda/produs/@* ....toate atributele din toate elementele PRODUS din COMANDA (5 soluii, 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 soluii, 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 interogri separate prin bar vertical: /comanda/produs/node()|/comanda/produs/@*

n acest fel, Xpath 1.0 permite reunirea soluiilor din mai multe ci. Xpath 2.0 ofer i tehnici mai avansate (intersecie, diferen etc.)

Ci cu rezultat boolean

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

/comanda/produs/id='p3' ...exist nodul ID cu coninutul (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 rdcin un fiu al crui coninut e stringul Calculator? (true)

/*/*/*="Ipod" ...are elementul rdcin un nepot cu coninutul Ipod? (true)

Ca regul general, interogrile booleene se termin cu o expresie logic care NU apare ntre paranteze ptrate (dac apare, nseamn c e o restricie aplicat nodului precedent, vezi mai jos).

Ci cu salt

Cnd nu suntem siguri pe care ramur i pe ce nivel se gsete rezultatul se pot realiza cutri cu salturi. n general nu sunt recomandate datorit performanelor 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 soluii)

//@id|//id ...returneaz toate IDurile din document, indiferent c sunt atribute sau elemente, indiferent cui aparin (3 soluii)

Ci cu restricie (condiie)

Restricia se pune ntre [] i poate fi aplicat oricrui nod din cale! Cele mai simple sunt restriciile de poziie:

/comanda/produs[1] ...returneaz primul PRODUS din COMANDA (atenie, 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 aparin (2 soluii)

//*[last()] ...returneaz toate elementele ce apar ultimele n cadrul nodului de care aparin (3 soluii, inclusiv rdcina, care e ultima din document!)

Sunt frecvente i retriciile privind coninutul:

/comanda/produs[id] ...returneaz acele PRODUSE din COMANDA care conin elemente ID (1 soluie)

/comanda/produs[@id] ...returneaz acele PRODUSE din COMANDA care conin atribute ID (2 soluii)

/comanda/produs[text()] ...returneaz acele PRODUSE din COMANDA care conin noduri text (2 soluii)

//*[text()="Calculator"] ...returneaz toate elementele care conin nodul text Calculator (1 soluie)

Restriciile permit folosirea lui or, not, and pentru condiii mai complexe:

/comanda/produs[@id or id] ....returneaz acele PRODUSE din COMANDA care au ID fie ca atribut, fie ca element (3 soluii; ne scutete de a scrie dou ci separate prin |)

/comanda/produs[not(@id)] ...returneaz acele PRODUSE din COMANDA care nu au atribut ID (1 soluie)

//produs[not(node())] ...returneaz acel PRODUS care este vid (1 soluie)

//produs[not(@*)] ...returneaz acel PRODUS care nu are atribute (1 soluie)

/comanda/produs[position()=1 and @id] ...returneaz acel PRODUS care ocup prima poziie (ntre frai) i are atribut ID (1 soluie) (cnd combinm restricia de poziie cu altele nu putem folosi produs[1 and @id], deoarece n expresii booleene orice numr diferit de zero e convertit n true, de aceea apelm la funcia position)

Restriciile nu se aplic doar pe ultima treapt din cale. Putem s le aplicm i undeva la mijlocul cii:

/comanda/produs[@id]/text()

...returneaz nodurile text din acele PRODUSE cu atribut ID, din COMANDA (1 soluie)

Ci cu axe Axele sunt direciile pe care nainteaz calea. n mod implicit, o cale merge n jos. n mod explicit putem schimba direcia cii cu: @ pentru a merge pe axa atributelor; // pentru a cuta n nodul curent i toii descendenii si; .. pentru a merge n sus, la printe; . pentru a face referire la nodul curent (folosit pentru a pune condiii asupra nodului curent).

n general .. se folosete n cile relative, ca i la foldere, pentru a urca un nivel n sus.

La foldere nu are sens s o lum n sus n ci absolute: C:\folder1\folder2\.. e echivalent cu C:\folder1! Aici ns are sens, cci e posibil s ajungem la un nod i pe alt cale dect cobornd prin printele su! (prin salt cu // sau o traversare orizontal peste frai)

//text()/.. ...returneaz prinii tuturor nodurilor text (7 soluii)

//id/ancestor::node() ...returneaz toi strmoii elementelor ID (3 soluii)

//text()*.=Calculator+ ...returneaz toate nodurile text egale cu Calculator (1 soluie)

Vedei cum s-a pus condiia asupra nodului curent cu ajutorul punctului!

Dac am fi scris //text()=Calculator am fi obinut o interogare boolean exist noduri text egale cu Calculator?? Dac am fi scris //node()*Calculator+ am fi obinut TOATE nodurile, deoarece expresia din paranteze trebuie s fie true sau false, iar prezena unui string oarecare e echivalat cu true! Am fi mai aproape de rezultat cu o construcie de genul //node()*text()="Calculator"+, ce d toate nodurile care conin un nod text egal cu Calculator (n timp ce punctul ne permite s returnm chiar nodurile text ce sunt egale cu Calculator)

n plus mai sunt utile cile 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 fraii!)

/comanda/produs[3]/following-sibling::node() ...acelai lucru, dar se returneaz numai fraii ce urmeaz (3 soluii)

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

/comanda/produs[@id=p2]/preceding::node()[preceding-sibling::comment()]/..

..returneaz printele 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 si de orice tip (2 la numr, un PRODUS i un comentariu), cu preceding::node() dintre acetia se alege doar acel predecesor care are un frate precedent de tip comentariu, cu [preceding-sibling::comment()] pentru acesta se caut printele, cu .. (e chiar rdcina)

Acesta e un exemplu complex care demonstreaz detalii pe care nceptorii le ignor: Putem varia axa la fiecare pas al cii; Putem folosi axe n restriciile dintre []; Putem folosi restricii la oricare pas al cii, nu doar la selecia rezultatului de pe ultimul pas.

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

Ci cu restricii bazate pe calcule/funcii

Funcia normalize-space elimin caracterele albe (spaiu, Enter, Tab) de la nceputul i sfritul unui string, iar n interiorul stringului pstreaz maxim un spaiu 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 rmne nimic din ele; 9 soluii)

//text()[normalize-space(.)!=""] ...returneaz toate nodurile text care nu sunt invizibile (6 soluii)

//text()[contains(.,"3")] ...returneaz toate nodurile text care conin caracterul 3 (2 soluii, observai din nou punctul folosit pentru a pune o condiie asupra nodului curent)

//node()[contains(.,"3")] ...returneaz toate nodurile indiferent de tip care conin caracterul "3" (6 soluii! Pe lng Idul i Pretul returnate i n cazul precedent, aici apar i toate nodurile care conin acel ID i acel PRET, deci inclusiv PRODUSUL al 3-lea i elementul rdcin: de reinut contains caut n toi descendenii, nu doar n coninutul textual direct)

//node()[count(*)=3] ...returneaz toate nodurile care au exact 3 elemente fiu (1 soluie)

//node()[count(node())=1] ...returneaz toate nodurile care au exact 1 nod fiu (5 soluii)

Mai multe funcii se prezint n seciunea urmtoare (toate pot fi folosite att ca restricii ct i pentru un calcul aplicat rezultatului final).

Ci cu rezultat bazat pe calcule/funcii

n aceast categorie intr cile asupra cror rezultate se aplic o funcie, de obicei una de agregare (sum, count etc.).

count(/comanda/node())

... returneaz numrul de noduri fiu ale rdcinii (numrul 11, nu 11 soluii!)

contains(/comanda/produs[3],"3") ... returneaz true, cci al 3lea PRODUS din COMANDA conine caracterul 3

sum(//produs/@pret) ... returneaz 300, suma atributelor PRET gsite 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 ci (Idul plus coninutul 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: funcia name e mecanismul prin care putem afla numele unui element sau atribut la care am ajuns pe alte ci dect cu ajutorul numelui (poziie, relaie cu alte noduri etc.)

string(/comanda) ... convertete rdcina n string (rezultatul fiind toate coninuturile textuale concatenate ntre ele)

substring(//produs[@pret=200],2,3) ...returneaz "alc", subirul decupat de la poziia 2, de lungime 3, din valoarea PRODUSULUI cu PRET=200

substring-before(/comanda/text()[normalize-space(.)!=""], "com") ...returneaz "Aceasta este o ", adic subirul decupat de la nceput pn la apariia lui "com", din acel nod text care nu este invizibil (exist i varianta substring-after)

string-length(/comanda/produs[2]) ...returneaz 10, numrul 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", obinut 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, funcia boolean e folosit pentru a converti orice tip de interogare ntr-un boolean, dac ne intereseaz doar existena unei soluii, nu i soluia n sine)

boolean(/comanda/*[position()=1 and self::produs]) ...este PRODUS numele primului element fiu din COMANDA? (true, funcia boolean se combin cu o declaraie de tip self ce exprim numele nodului curent, pentru a-i testa numele)

Important: funcia position nu poate fi folosit pentru a calcula poziia unui nod! Deci nu putem calcula position(/comanda/produs[id]) pentru a obine poziia ntre frai a PRODUSULUI ce are un fiu ID!

Vom apela la un artificiu, obinnd poziia prin numrarea frailor precedeni: count(/comanda/produs[id]/preceding-sibling::node()) ...returneaz 7 (numr toi fraii precedeni, indiferent de tip)

Important: XPath 1.0 este foarte limitat n ce privete funciile de agregare are doar sum i count. Alte operaii (medie, produs etc.) se pot realiza fie cu XPath 2.0 (ce permite structuri FOR i IF chiar n ci), fie se paseaz responsabilitatea spre limbajul de programare ce solicit interogarea.

Ci relative

Cile relative se calculeaz relativ la un nod curent. n Oxygen putem seta nodul curent printr-un click pe un marcator (care primete subliniere gri). Dai un click pe al 3lea PRODUS.

Cile relative nu mai ncep cu slash:

following::* ...returneaz elementul urmtor (PRETTOTAL)

* ...returneaz toate elementele fii ai nodului curent (3 soluii)

../*[1] ...returneaz primul element fiu al printelui nodului curent (primul PRODUS)

../node()[1] ...returneaz primul nod fiu al printelui nodului curent (primul nod invizibil)

count(*) ...returneaz numrul de elemente fii ai nodului curent (3)

string-length(*[last()]) ...returneaz lungimea coninutului ultimului element fiu al nodului curent (4)

preceding-sibling::node() ...returneaz precedenii frai ai nodului curent (7 soluii, inclusiv noduri invizibile i comentariul)

count(preceding-sibling::*[not(@*)]) ...ci din fraii precedeni nu au atribute (0)

preceding-sibling::*/@pret=100 ...exist ntre fraii precedeni vreunul cu atributul PRET=100? (true)

id="p3" ...are nodul curent un fiu id cu coninutul "p3"? (true)

boolean(self::produs) ...are nodul curent numele produs? (true)

count(preceding-sibling::node()) ...care e poziia, ntre frai, a nodului curent? (7) deoarece nu putem folosi funcia position(), se apeleaz la acest truc de numrare a frailor precedeni

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 cptat ntietate datorit unor avantaje precum: permite limitarea tipurilor i intervalelor de date permise ntr-un document; permite validare parial/multipl (cnd un document are marcatori din mai multe vocabulare); permite constrngere prin expresii regulate; permite definire unor restricii de tip "cheie primar/cheie strin" (deci i relaii ntre documente) mai flexibile dect DTD; este el nsui 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 urmtoarele reguli: elementul rdcin s fie Comanda; o Comanda s conin obligatoriu maxim 3 elemente Produs (deci ocurena min.1,max.3) urmate de 1 element opional Onorata (min.0,max.1): Produs s conin 2 atribute obligatorii (CodProdus, Pret) i un nod text obligatoriu (denumirea produsului);

CodProdus s aib valoare string unic ("cheie primar la nivel de document") de exact 5 caractere; Pret s aib valoare numeric pozitiv ntreag; Nodul text s accepte doar una din valorile Televizor, Calculator, Ipod;

Onorata s conin un nod text obligatoriu, cu o valoare boolean.

Filozofia Tipurilor

Filozofia XML Schema se bazeaz pe noiunea de TIP, care e mai larg dect la alte limbaje. Un tip poate fi: TIP PREDEFINIT: orice tip de date clasic (string, integer, plus cteva specifice XMLului precum tipul ID stringuri irepetabile, sau tipul Name stringuri fr spaii, sau tipul token stringuri cu maxim un spaiu ntre cuvinte) ; TIP SIMPLU: o submulime a unui TIP PREDEFINIT (o list de stringuri, un interval de valori numerice etc.) sau o reuniune de submulimi; TIP COMPLEX: un model complex obinut prin mbinarea de marcatori, noduri text i atribute, ce se dorete a fi reutilizat (o structur intern reutilizabil a unui nod element).

n general e o idee bun ca pentru fiecare marcator i atribut prevzut a aprea prin documente: s se defineasc un TIP cu toate regulile privind acel marcator/atribut; apoi s se declare apartenena marcatorilor/atributelor la TIPURILE definite.

n felul acesta, regulile capt oarecare modularitate se pot edita regulile la nivelul unui TIP, fr a afecta marcatorii care nu au legtur cu acesta.

Obs: n general se evit crearea unui TIP pentru elementul rdcin, avnd n vedere unicitatea lui,nu se prea pune problema reutilizrii sale. Sunt totui situaii n care i structura rdcinii se poate reutiliza ducnd la recursivitate, cnd copiii rdcinii motenesc structura rdcinii, nepoii la fel, etc. ramificnd arborele DOM n maniera fractalilor, teoretic la infinit. Practic nu se merge la infinit dac structurii recursive i se aloc un caracter opional (astfel nct nodurile de pe ultimul nivel s poat s nu mai conin nimic).

Planificarea tipurilor

Tipurile se definesc ntr-o manier bottom-up, pe baza cerinelor, pornid de la nodurile simple ale viitoarelor documente (atributele i nodurile text) apoi urcnd 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 restricie de lungime exact 5 caractere;

pentru atributul Pret nu are sens s crem un tip, exist TIP PREDEFINIT pentru numere pozitive

2. Tipuri pt nodurile simple (care conin 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 crem un tip, exist TIP PREDEFINIT pentru valori booleene.

3. Tipuri pt noduri complexe (care conin i altceva dect text): vom crea un TIP COMPLEX pentru Produs: o tipul va fi o structur alctuit 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 crem un TIP pentru Comanda, cci nu dorim recursivitatea structurii rdcinii.

Ca regul general: TIPURILE SIMPLE se creeaz pentru atribute sau elemente ce vor conine doar text;

TIPURILE COMPLEXE se creeaz pentru elemente care pot avea i altceva n afar de un coninut text (atribute sau elemente copil).

Crearea vocabularului

Crem un fiier 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 generm automat codul surs XML Schema.

n modul Design, cea mai mare parte din munc se realizeaz n meniul click dreapta, cu primele dou opiuni: New Global ne permite s construim TIPURILE; Edit Attributes ne permite s configurm TIPURILE sau alte componente ale vocabularului

Avertismente: Edit Attributes NU se refer la atribute XML, ci la diverse proprieti ale vocabularului (e o coinciden de denumire care risc s v duc n eroare); Panoul Attributes din dreapta ofer teoretic aceleai opiuni cu Edit Attributes, dar Edit Attributes adesea ofer i unele opiuni n plus, aa c evitai s folosii panoul.

Crearea tipurilor

Se ncepe cu TIPURILE SIMPLE, ntr-o abordare bottom-up, de la simplu la complex:

TIPUL SIMPLU pentru denumirile de produse: click dreapta New Global Simple Type dm 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 jumtatea de sus a ferestrei alegem TIPUL PREDEFINIT: Base Type = xs:string n seciunea Facets definim filtrul: Enumerations, apoi cu click dreapta Add adugm pe rnd valorile permise

TIPUL SIMPLU pentru CodProdus: click dreapta (pe grupul SCHEMA) New Global Simple Type dm tipului numele ModelCoduri click dreapta pe ModelCoduri Edit Attributes TIPUL PREDEFINIT: BaseType=xs:ID filtru: length 5

TIPUL COMPLEX pentru Produs: click dreapta pe SCHEMA New Global Complex Type; dm tipului un nume (ModelProduse)

Mai nti ne ocupm de coninutul textual al viitoarelor Produse: click dreapta pe ModelProduse Edit Attributes; alegem TIPUL coninutului: BaseType = ModelDenumiri (iat cum integrm 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 absena coninutului!)

Apoi ne ocupm de atribute: click dreapta pe ModelProduse Append Child Attribute dm atributului un nume (CodProdus), apoi ne ocupm de el: o o alegem TIPUL atributului: Type=ModCoduri (de unde va moteni toate restriciile) alegem obligativitatea: Use = required

click dreapta pe ModelProduse Append Child Attribute dm atributului nume: Pret: o o i alegem TIPUL PREDEFINIT: Type=xs:positiveInteger alegem obligativitatea: Use=required

n acest moment toate TIPURILE sunt create:

Crearea vocabularului Asta nseamn crearea rdcinii i structurii sale interne. Click dreapta pe SCHEMA New Global Element i dm 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 dm nume: Produs;

click dreapta pe SECVEN Append Child Element o i dm nume: Onorata

Le stabilim limitele de ocuren, caracterul opional (min.ocur.=0) sau obligatoriu (min.ocur.=1), precum i TIPUL coninutului: click dreapta pe Produs Edit Attributes:

o o

TIP coninut: Type=ModelProduse Min.Occur. =1, Max.Occur.=3 (obligatoriu, maxim de 3 ori)

click dreapta pe Onorata Edit Attributes: o o TIP: Type=xs:boolean Min.Occur.=0, Max.Occur.=1 (opional, maxim o dat)

Vocabularul final arat astfel:

Tipurile

Vocabularul propriu-zis

n modul Text putei vizualiza codul surs. Nu e foarte complicat, dar e mult de scris (se poate observa pe codul surs c limbajul XML Schema nsui e un vocabular XML). Salvm vocabularul cu numele vocabular.xsd

Validarea Crem un document XML nou. De data aceasta, la creare apsm Customize pentru a indica vocabularul, n caseta Schema URL. nc de la creare, documentul va conine un element Produs, iar rdcina va fi nsoit de o serie de atribute care s fac legtura cu vocabularul (noNamespaceSchemaLocation).:

<?xml version="1.0" encoding="UTF-8"?> <Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd"> <Produs CodProdus="" Pret=""></Produs> </Comanda> Prezena unui Produs vid e explicabil prin caracterul su obligatoriu. Cnd un document e construit pe baza unui vocabular, se pot genera automat o serie de noduri obligatorii sau valori implicite. La apsarea butonului de validare primim numeroase erori:

Putem testa pe rnd regulile vocabularului, scriind un document corect, apoi nclcnd regulile una cte una, pentru a ne convinge de efectul acestora. Urmtorul 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> Dei lipsete elementul Onorata, nu e o problem, acesta fiind definit cu caracter opional. 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 urmtorul document:

<?xml version="1.0" encoding="UTF-8"?> <Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd"> <Produs CodProdus="aaaaa" Pret="2">Televizor</Produs> <Onorata>true</Onorata> </Comanda> Posibil subiect de examen: s primii un vocabular i un document i s gsii invaliditile.

Modificarea vocabularelor Nu e obligatoriu ca la crearea unui vocabular s se lucreze cu TIPURI, aa cum am lucrat n acest mod. Se pot impune toate restriciile direct pe nodurile vocabularului, fr a mai crea tipuri.

Totui, tipurile ofer avantaje legate de reutilizare i modularitate: mai multe atribute/elemente pot s aib acelai tip, deci aceeai gam de valori/structur intern permis (vezi exemplul urmtor); se pot crea tipuri din alte tipuri (s-a vzut deja cum ModelProduse include ModelDenumiri i ModelCoduri);

se pot aduce modificri la un tip fr a afecta restul vocabularului, care nu are legtur cu acel tip (vezi mai jos).

De exemplu, n cazul de fa dorim urmtoarele modificri: Codurile de produse s aib structura: litera P, urmat de exact 3 cifre, urmate de cel puin nc un caracter oarecare. n loc de Onorata s poat s mai apar un grup de maxim 3 elemente ProdusIndisponibil, cu aceeai structur intern ca i elementele Produs.

Pentru a modifica structura codurilor de produse e suficient s modificm TIPUL ModelCoduri modificarea se va propaga peste tot n vocabular unde avem atribute (sau elemente!) de acest tip. Click dreapta pe ModelCoduri Edit Attributes Renunm la restricia length=5 (din panoul Facets) n locul ei folosim restricia pattern = P[0-9]{3}.+

Iat o calitate foarte important a XML Schema posibilitatea de a constrnge 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 lociitor pt un caracter, + e indicator de repetiie 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 pn se lipete de nodul Choice click dreapta pe nodul Choice Append Child Element i denumim noul element cu numele ProdusIndisponibil.

Mai trebuie doar s precizm c ProdusIndisponibil reutilizeaz aceeai structur ca i elementele Produs, apoi s-i stabilim ocurenele: click dreapta pe ProdusIndisponibil Edit Attributes o Min.Occurs=1, Max. Occurs=3

Type=ModelProduse

Forma final a vocabularului va fi urmtoarea:

Un exemplu de document valid ar fi urmtorul: <?xml version="1.0" encoding="UTF-8"?> <Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd"> <Produs CodProdus="P999xx" Pret="200">Televizor</Produs> <Produs CodProdus="P1111" Pret="50">Ipod</Produs> <ProdusIndisponibil CodProdus="P123q" Pret="400">Calculator</ProdusIndisponibil> </Comanda>

Dar documentul de mai jos? <?xml version="1.0" encoding="UTF-8"?> <Comanda xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="file:/C:/Users/2/Documents/vocabular.xsd"> <Produs CodProdus="P999xx" Pret="200">Televizor</Produs> </Comanda>

i acesta e valid, chiar dac lipsesc att ProdusIndisponibil ct i Onorata! Asta din cauz c Choice ne permite s alegem ntre 1-3 ProdusIndisponibil i 0-1 Onorata. Practic lipsa prii de dup Choice nseamn c am optat pentru Onorata dar am profitat de caracterul opional i nu l-am mai pus deloc.

XSLT

XSLT este limbajul de transformare a documentelor XML i se bazeaz pe Xpath pentru a extrage informaii 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 rspunsului de la server n cod HTML).

Pornim de la urmtorul 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>

Crem n Oxygen urmtoarea 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 conine una sau mai multe reguli de substituire cu dou componente:

<xsl:template match="/"> Text oarecare </xsl:template>

match este calea Xpath a elementelor care vor suferi substituia coninutul lui xsl:template este noul coninut care va intra n locul celui detectat de match.

Exemplul de fa selecteaz ntreg documentul (calea XPath "/" reprezint documentul n ansamblul su) i l nlocuiete cu stringul "Text oarecare".

Pentru a executa transformarea, Oxygen impune s se creeze un scenariu de transformare (diverse configurri).

Crearea scenariului e solicitat cnd ncercm s executm transformarea.

Tipul scenariului va fi XSLT transformation dac documentul activ din Oxygen e chiar transformarea (dac e XMLul original, alegem tipul XML transformation with XSLT). Crem un scenariu nou.

Setrile eseniale sunt n prima i ultima categorie. Categoria FO Processor se folosete doar dac facem transformri de tip XSL-FO (o alternativ la CSS, dedicat formatrii de cod XML).

Avem deja completat calea foii XSLT curente (codul currentFileURL e suficient). Trebuie s selectm 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 precizm ce s se ntmple cu rezultatul transformrii, n categoria Output:

Decidem s salvm fiierul cu numele rezultat.xml i s deschidem imediat rezultatul n Oxygen.

Apoi apsm OK i Transform now. Rezultatul va fi:

<?xml version="1.0" encoding="utf-8"?>Text oarecare

Se observ c nu e obligatoriu ca XSLT: s returneze documente XML bine formate;

s returneze buci 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 coninutul fiierului original! Dac nu punem nimic n regula de substituire, pur i simplu se returneaz un document gol. Dac vrem s conservm ntocmai unele elemente din fiierul original, trebuie s definim explicit o regul de conservare (=o regul care substituie acele elemente cu ele nsele). Revenim mai trziu la regulile de conservare.

Obs: Dac outputul transformrii este un fiier XML, caracterele invizibile (spaiu, 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 interogri XPath i s se aplice o transformare fiecreia dintre acestea. Adesea ele sunt mbinate cu regulile de substituire alternativ (IF i CASE) care aplic substituii diferite n funcie de o interogare XPath boolean.

n exemplul urmtor, pentru fiecare produs din COMANDA se va returna cuvntul Produs scris cu italic (ntr-o pagin HTML):

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:for-each select="comanda/produs"> <i>Produs</i> </xsl:for-each> </xsl:template> </xsl:stylesheet>

Obs: de data aceasta generm cod HTML; am bifat i opiunea de afiare n browser a rezultatului salvat.

Rezultat: <?xml version="1.0" encoding="utf-8"?><i>Produs</i><i>Produs</i><i>Produs</i>

Modificm exemplul pentru a afia cuvintele unul sub cellalt i pentru a concatena o numerotare dup fiecare (Produs1, Produs2 etc.), adic echivalentul unui ciclu FOR de genul urmtor (dac am fi n PHP):

for ($i=0;$i<3;$i++) print "<i>Produs</i>".$i."<br/>";

O problem spinoas pentru nceptori 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).

Iniializarea unei variabile se realizeaz cu una din construciile: <xsl:variable name="i" select="0"/> (dac valoarea este una simpl, echivalentul lui $i=0 n PHP, sau dac valoarea e cod XML obinut printro 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 orict de complex)

n funcie de coninutul variabilei, apelarea sa se face diferit: <xsl:value-of select="$i"/> (dac valoarea e simpl sau dac e cod XML din care vrem s lum doar coninutul textual) <xsl:copy-of select="$i"/> (dac valoarea e cod XML i vrem s-l lum aa cum e, cu tot cu marcatori)

Problema este c nu putem iniializa de 2 ori aceeai variabil. Rostul lor uzual este s asigure reutilizabilitatea unei buci de cod XML. De exemplu precedenta transformare poate s aib i urmtoarea form, avnd n vedere c nu facem dect s reutilizm aceeai 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>

nceptorii sunt tentai s ignore faptul c variabilelor XSLT nu li se pot reatribui valori, i vor ncerca s implementeze un ciclu FOR care s afieze 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 iniializat. Rezultatul va fi afiarea repetat a lui Produs1.

Totui, 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 pasm responsabilitatea contorizrii spre Xpath. Practic, nu dorim dect s facem o numrtoare a produselor. Dac v mai amintii din XPath, poziia unui element ntre fraii si se obine prin numrarea frailor precedeni.

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

Reinei acest truc i faptul c sunt numeroase situaiile n care lipsa de flexibilitate a variabilelor din XSLT poate fi compensat prin generarea unor valori variabile cu funcii Xpath.

n general linia

<xsl:value-of select=".........."/> este una din cele mai des ntlnite din XSLT. Cu ajutorul ei se pot genera: att valori calculate n XSLT (cu variabile); ct i valori extrase cu Xpath din documentul original.

Obs:Mai remarcai i faptul c adugarea de coninut nou (HTML n acest caz) se face prin simpla scriere de cod nou n interiorul lui xsl:template, fr s fie necesar un operator de concatenare explicit!

Reguli de substituire recursiv

Atunci cnd nici Xpath nu ne ajut n a depi problema rigiditii variabilelor, mai avem o soluie la ndemn regulile recursive.

O regul recursiv e o regul ce se apeleaz pe ea nsi, de obicei cu parametri. Mecanismul e similar cu funciile recursive din alte limbaje: regula XSLT se comport ca o funcie, parametrii ei se comport ca argumente ale unei funcii.

Problema cu numrtoarea produselor se poate rezolva i astfel:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:call-template name="generare"> <xsl:with-param name="listaproduse" select="comanda/produs"/> </xsl:call-template> </xsl:template> <xsl:template name="generare"> <xsl:param name="i" select="0"/> <xsl:param name="listaproduse"/>

<xsl:if test="$listaproduse"> <i>Produs<xsl:value-of select="$i"/></i> <xsl:call-template name="generare"> <xsl:with-param name="i" select="$i+1"/> <xsl:with-param name="listaproduse" select="$listaproduse[position()>1]"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet>

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

Un apel de "funcie" e format din dou componente: xsl:call-template indic numele funciei apelate; xsl:with-param indic numele i valoarea argumentelor cu care se apeleaz funcia (n interiorul funciei, acestea trebuie declarate cu xsl:param i cu aceleai nume ca i argumentele din apel).

De exemplu secvena (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 construcii de forma (sintaxa e orientativ, nu aparine unui limbaj anume):

listaproduse=<vectorul produselor din comanda> generare(listaproduse)

n timp ce a doua regul s-ar traduce prin:

function generare(listaproduse,i=0) { if (listaproduse) write "Produs"+i i=i+1 listaproduse=<vectorul listaproduse fara primul element> generare(listaproduse,i) }

Observai diferena ntre modul de lucru al variabilelor i modul de lucru al parametrilor. Parametrii i pot schimba valoarea la apelul unei "funcii" deci nu sunt la fel de rigizi ca variabilele! De aceea putem simula cicluri FOR prin funcii care se apeleaz pe ele nsele cu un argument $i care tot crete la fiecare apel.

Aici parametri/argumentele sunt: $i, iniializat cu zero la primul apel i incrementat apoi la fiecare apel ce urmeaz; $listaproduse, iniializat cu vectorul produselor la primul apel, i modificat prin eliminarea treptat a cte unui produs, la apelurile urmtoare, pn se golete (care e i condiia de terminare).

Condiia de continuare a recursivitii e <xsl:if test="$listaproduse">, adic oprim ciclul atunci cnd vectorul listaproduse rmne gol (s-au terminat de eliminat produsele din el).

Cum aminteam, recursivitatea se folosete adesea atunci cnd dorim s implementm cicluri FOR n care o variabil i tot schimb valoarea, iar aceast schimbare nu poate fi pasat spre Xpath (de exemplu cnd calculm produsul unor elemente, cci nu exist funcia "product" n Xpath2).
2

XPath 1.0 are doar 2 funcii de agregare: sum i count.Restul operaiilor iterative (medie, produs, etc.) trebuie s aib loc n limbajul care gzduiete Xpath. Xpath 2.0 n schimb permite s se implementeze cicluri FOR chiar n ci, fcnd posibile astfel de operaii iterative direct n ci 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 anunat planuri de dezvoltare a browserelor n aceast direcie, tendina acestora fiind s consolideze rolul lui JavaScript, nu s l substituie cu XSLT!).

Reguli de substituire alternativ

Observai c pn aici am generat un document nou fr s folosim coninutul existent n cel original (nu chiar deloc, am folosit poziia produselor n ultimul exemplu). Rolul uzual al unei transformri XSLT este s genereze cod i coninut nou concatenat cu cod i coninut din cel vechi. n urmtorul exemplu, n locul cuvintelor Produs1, Produs2, Produs3, vom genera denumirile produselor.

Aceast sarcin e puin complicat de faptul c documentul XML original are o structur neregulat primul produs are denumirea ntr-un atribut, al doilea l are n coninutul textual, al treilea l are ntr-un element-fiu. Deci, chiar dac parcurgem cu un ciclu FOR cele 3 produse, va trebui s implementm 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 conine un ir de variante definite cu xsl:when (fiecare cu cte o condiie testat cu atributul test). Ultima variant este xsl:otherwise (pentru cazul n care nici una din ramuri nu a funcionat).

Ce face structura CASE? Dac gsete un atribut denumire (test="@denumire") i extrage valoarea (cu xsl:value-of); Dac gsete un element denumire (test="denumire") i extrage valoarea; Dac nu gsete nici una din variante (otherwise), va considera c denumirea se afl n coninutul textual, deci valoarea elementului ("." reprezint nodul curent n Xpath).

Observai c toate cile Xpath din atributele test i select sunt RELATIVE la nodul curent (produsul la care s-a ajuns cu xsl:for-each); la rndul su, atributul select de la for-each e o cale RELATIV la nodul selectat de match.

Reinei c n general, XSLT folosete ci RELATIVE la nodul pe care s-a poziionat instruciunea-printe (cea cu match e printe pt for-each, acesta e printe pt choose i when etc.). Asta cu excepia atributului match care folosete ci absolute (chiar dac nu punem slash la nceputul lor!)

Structura CASE nu e singura metod prin care putem obine cele 3 denumiri! Dup cum am vzut la exemplul cu numerotarea, XSLT conlucreaz cu Xpath ntr-o manier flexibil unele sarcini pot fi transferate spre Xpath, inclusiv filtrarea/selecia de elemente.

Putem s transferm responsabilitatea seleciei i spre Xpath, cu acelai rezultat ca precedentul exemplu:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/">

<xsl:for-each select="comanda/produs"> <xsl:value-of select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/> <hr/> </xsl:for-each> </xsl:template> </xsl:stylesheet>

Observai cum am evitat structura CASE mutnd decizia spre Xpath: self::node()[not(denumire) and not(@denumire)] returneaz valoarea nodului curent cu condiia 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 soluiilor celor 3 interogri. Cum fiecare PRODUS e doar n una din cele 3 situaii, se va returna valoarea dorit pt. fiecare caz!

O a treia variant ar fi s evitm cu totul ciclul FOR (ceea ce nu e chiar recomandat, dar aici numrul de produse e suficient de mic nct 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 iari o parte din sarcini spre Xpath: de data asta exploatm atributul match al regulii n loc s substituim tot documentul original, procedm astfel: i substituim doar produsele (punnd n match un Xpath care ne d toate produsele); Nodurile care nu sunt produse le tergem (cu o regul general de substituire cu irul vid).

Primul aspect e rezolvat astfel (observai cum match nu mai selecteaz rdcina, 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 rulai aceast transformare, vei observa: Produsele se substituie; Dintre restul nodurilor se conserv doar nodurile de tip text (inclusiv cele invizibile)! Aceasta e o regul implicit de funcionare n XSLT: orice nod care nu e afectat de nici o substituire e eliminat, cu excepia 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, neconinnd nimic, le substituie pe toate cu irul vid, deci le terge.

Obs1. n atributele match ni se permite s nu mai punem cele 2 slashuri, deci match="text()" e echivalent cu match="//text()". Acest lucru e permis deoarece oricum valoarea lui match va fi considerat cale absolut de cutare. E o mic abatere de la sintaxa XPath standard, dar e suportat de XSLT datorit frecvenei cu care se fac match-uri prin cutare (fr 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), interogrile XPAth joac rolul lui SQL, iar transformrile XSLT joac rolul unui generator de rapoarte. "Rapoartele" finale sunt fie pagini Web (caz n care XSLT joac rolul PHPului), fie buci de pagini Web (tabele, structuri de div-uri) ce urmeaz s fie inserate ntr-o pagin mai mare cu metode Ajax.

Modificm n continuare exemplul pentru a genera din codul XML o pagin HTML mai cuprinztoare, care s conin: Un titlu; Un tabel cu 2 coloane: denumirile i preurile; Dedesubt, o celul mare (COLSPAN) cu suma preurilor.

Am amintit la un moment dat c un document XML nu ar trebui s conin noduri calculate (de ex. totaluri) dect dac e ntr-un stadiu final, gata de listare/afiare 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 totui c el nu exist acolo i l vom calcula n cadrul transformrii.

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <html> <head> <style> #cap {color:red} </style> </head> <body> <h1>Lista produselor din comanda</h1> <table border="1"> <tr id="cap"><td>Denumire</td><td>Pret</td></tr> <xsl:for-each select="comanda/produs"> <tr> <td> <xsl:value-of select="self::node()[not(denumire) and not(@denumire)]|denumire|@denumire"/> </td> <td> <xsl:value-of select="pret|@pret"/> </td> </tr> </xsl:for-each> <tr> <td colspan="2" align="right"> <xsl:value-of select="sum(//pret|//@pret)"/> </td> </tr> </table> </body> </html> </xsl:template> </xsl:stylesheet>

Observai c s-a generat o pagin Web complet, cu tot cu CSS (ar putea conine chiar i JavaScript!) Din acest exemplu reiese posibilitatea de a folosi XSLT ca generator dinamic de pagini Web, asemntor cu rolul pe care l are PHP!

Regula de conservare

S-a vzut la nceput c XSLT nu conserv implicit codul XML original. Dac dorim s se conserve anumite elemente, trebuie s crem o regul de conservare care s substituie elementele cu ele nsele:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:copy-of select="."/> </xsl:template> </xsl:stylesheet>

Aceast regul substituie elementul rdcin cu el nsui, producnd o copie a documentului original (pentru execuie, modificai scenariul de transformare s nu se mai returneze html n browser).

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:copy-of select="."/> </xsl:template> </xsl:stylesheet>

Aceast regul produce tot o copie a ntregului document, dar elimin nodurile invizibile.

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/comanda/produs">

<xsl:copy-of select="."/> </xsl:template> </xsl:stylesheet>

Aceast regul conserv toate elementele produs. Pe lng ele, datorit regulii implicite, se conserv i nodurile text din afara produselor. Studiai diferena fa de urmtorul 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. Diferena e c se conserv produsele dar FR coninut i atribute! Aceast tehnic se folosete cnd vrem s conservm elemente existente dar s le redefinim coninutul 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 retastm elementul original, cu modificrile dorite aplicate asupra lui:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/comanda/produs"> <produs PretNou="{@pret|pret}"> <NouFiu>Nou continut</NouFiu> </produs> </xsl:template> </xsl:stylesheet>

Observai cum am creat un atribut PretNou care i ia valoarea fie din atributul vechi pret, fie din elementul pret (depinde pe care-l gasete).

<?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, coninutul produselor e redefinit s fie egal cu preul (luat fie din atributul pret, fie din elementul-fiu pret, de la caz la caz).

n urmtorul caz conservm produsele, dar i coninutul lor textual.

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/comanda/produs"> <xsl:copy> <xsl:apply-templates/> </xsl:copy> </xsl:template> </xsl:stylesheet>

Instruciunea xsl:apply-templates se utilizeaz de obicei mpreun cu xsl:copy i are rolul ca, dup ce s-a fcut copia goal a elementului, s o umple cu rezultatul eventualelor reguli care mai exist pentru fiii elementului. n cazul de fa, neexistnd alte reguli, se aplic regula implicit de conservare a nodurilor de tip text, deci obinem produsele fr atribute dar cu coninutul 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, cnd o gsete pe a doua (aplicabil fiilor de tip element i fiilor de tip text vizibil), o aplic. Conform acesteia, toi fiii din produs care sunt ori element, ori nod text vizibil, vor fi nlocuii cu textul din interiorul regulii.

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/comanda"> <xsl:copy> <xsl:attribute name="client">Pop Ion</xsl:attribute> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template match="produs"> <xsl:copy> <xsl:attribute name="PretNou"> <xsl:value-of select="@pret|pret"/> </xsl:attribute> <ID><xsl:value-of select="@id|id"/></ID> <xsl:apply-templates/> </xsl:copy> </xsl:template> <xsl:template match="text()[normalize-space(.)!='']"/> </xsl:stylesheet>

Prima regul: copiaz o versiune goal a rdcinii comanda, i adaug atributul client i o umple cu rezultatul eventualelor reguli care exist pentru fiii si.

Astfel se ajunge la a doua regul care:

realizeaz versiuni goale ale produselor, le creeaz atributul PretNou cu valoarea luat din preurile existente (fie din atributul vechi pret, fie din elementul pret) le adaug subelemente ID cu valoarea luat din id-urile vechi (atribute sau elemente) i mai adaug rezultatul eventualelor reguli care mai exist pentru fiii produselor.

A treia regul caut noduri text vizibile i le terge. Ea e apelat att de prima regul (tergnd nodurile 600 i "Aceasta este o comanda" care s-ar crea datorit regulii implicite de conservare a textului) ct i de a doua regul (tergnd textul vechi din produse, care s-ar conserva datorit aceleiai reguli implicite).

Concluzie: Reinei regulile implicite din XSLT: Codul documentului original nu e conservat n mod implicit, necesit o regul de conservare (totui, 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 incidena nici unei reguli de substituire se elimin, cu excepia nodurilor text care se conserv (i concateneaz ntre ele); Dac un acelai 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 dect match="node()").

Cum putem rula transformri XSLT fr Oxygen?

1. XSLT 1.0 e suportat de orice browser. Foaia de transformare se ataeaz documentului XML original ntr-o manier asemntoare cu modul de ataare 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 rspunsului XML venit de la server, pentru a produce o bucat de cod HTML inserabil n pagin.

Procedura implic browser sniffing cci are sintax diferit de la un browser la altul; Putem folosi librria Sarissa care mascheaz diferenele dintre browsere; Transformarea XSLT executat n JavaScript (deci n browser) poate substitui operaiile clasice de manipulare a paginii prin JavaScript.

3. Putem aplica XSLT i pe server, inclusiv n PHP, astfel nct s livrm spre JavaScript un rspuns 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 ct posibil spre browser, minimiznd efortul serverului; n plus, dac JavaScript controleaz transformarea, poate s foloseasc datele originale i la altceva. Transformarea XSLT executat n PHP e frecvent folosit cnd 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 informaii din codul original (ca i n cazul JavaScript, XSLT devine o alternativ la operaiile clasice de manipulare DOM).

n practic, cel mai des ntlnite sunt transformrile pe server, deoarece documentele XML circul mai mult ntre servere dect ntre server i client (unde JSON tinde s fie preferat).

n acest seminar exemplificm doar prima variant:

Fiierul 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 legtura cu foaia de transformri. Dac dorim ca, n loc de transformare, s aplicm o foaie de stiluri CSS, nlocuim doar fiierul din HREF. Pentru a funciona legtura, salvai foaia de transformri cu numele transf.xsl:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <html> <body> <h1>Comanda lui <xsl:value-of select="Comanda/@Client"/></h1> <table border="1"> <tr> <td>Denumire</td> <td>Pret</td> </tr> <xsl:for-each select="Comanda/Produs"> <tr> <td><xsl:value-of select="."/></td> <td><xsl:value-of select="@Pret"/></td> </tr> </xsl:for-each> <tr> <td colspan="2" align="left"> Total: <xsl:value-of select="sum(Comanda/Produs/@Pret)"/> </td> </tr> </table> </body> </html> </xsl:template> </xsl:stylesheet> Din acest moment, dac deschidei fiierul XML n browser, vei 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 urmtoarele exemple vom folosi AJAX pentru a transfera structuri de date mai complexe, serializate conform standardelor XML i JSON. Adesea aceste structuri de date sunt create pe server din diverse surse o baz de date sau un serviciu Web.

Construirea unui rspuns XML n PHP se poate realiza pe mai multe ci: preluare dintr-un fiier XML existent; iniializare 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 fiier 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 construiete din date obinute din alte surse (baz de date, servicii Web, depozit de documente), mai rar este disponibil gata de utilizare ntr-un string sau fiier. Sunt totui i situaii n care XMLul e disponibil pe server n fiiere, de exemplu cnd se lucreaz cu depozite de documente XML (am sugerat la curs c ele pot teoretic substitui funcionalitatea unei baze de date relaionale).

Odat ajuns n JavaScript, datele pot fi extrase din rspunsul XML prin: funciile standardului DOM; transformri XSLT (caz n care i foaia de transformare trebuie cerut de la server, preferabil tot prin AJAX pentru a nu ntrerupe procesele din pagin!) funciile E4X (mai uor, dar slab suportat de browsere); interogri Xpath;

Vom prezenta cteva din aceste combinaii posibile, plus metoda n care datele sunt transferate n format JSON, care e cea mai facil, dar nu are posibiliti la fel de complexe (nu putem aplica transformri XSLT sau validri pe JSON, ci apelm 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 cnd avem deja codul XML stocat ntr-un string PHP (scriei-l pe un singur rnd ca s nu apar noduri invizibile i salvai cu numele xml.php):

<?php header('Content-type:application/xml'); $sirxml='<?xml version="1.0" encoding="UTF-8"?><Comanda Client="PopIon"><Produs ID="P1" Pret="100">Televizor</Produs><Produs ID="P2" Pret="30">Ipod</Produs></Comanda>'; print $sirxml; ?>

Obs: header seteaz tipul de rspuns pe care PHP l d browserului dac nu setm tipul XML, browserul l va recepiona ca text simplu i va trebui realizat efort suplimentar n JavaScript pentru a-l converti n XML;

Putei verifica faptul c n browser ajunge un arbore DOM i nu un simplu string, accesnd direct scriptul de mai sus (http://localhost/xml.php), apoi eliminai linia header i vedei cum arat rspunsul n browser n lipsa declaraiei Content-type!)

Construim o pagin HTML care s solicite acest cod XML prin Prototype, la trecerea mouseului peste un DIV:

<!DOCTYPE html> <html> <head> <script src="scriptaculous/lib/prototype.js"></script>

<script> function cheamaxml() { config={method:"GET",onSuccess:proceseaza} new Ajax.Request("xml.php",config) } function proceseaza(xhr) { alert(xhr.responseText) } </script> </head> <body> <div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()"> Treci cu mouseul pe aici pentru a chema documentul XML de la server </div> <div id="x">Aici se va insera informatia din XML</div> </body> </html>

Obs: am folosit Ajax.Request din Prototype pentru schimbul asincron; nu am pus parameters, cci nu trimitem nimic la server, doar solicitm rspunsul acestuia (deci e un rspuns static, care nu e influenat de trimiterea de date din browser); rspunsul e afiat 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 funciile DOM standard getElementsByTagName (dar fr getElementById!!!), childNodes, firstChild, nodeValue,nodeName,getAttribute etc. o o avantaj: funcioneaz similar n toate browserele, dezavantaj: necesit uneori parcurgeri anevoioase, traversri de arbore i combinaii 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 fr a interaciona (prea mult) cu arborele DOM al paginii (mare pare din rolul JavaScript e preluat de XSLT); dezavantaj: trebuie s transferm de la server i o foaie XSLT cu regulile de transformare (uneori e mai eficient s facem transformarea direct n PHP i s dm rspunsul gata transformat).

metoda Xpath: o o avantaj: interogri mult mai concise dect funciile DOM, i cu rezultate mai complexe; dezavantaj: funciile JavaScript care gzduiesc interogrile (i le preiau rezultatele) sunt nc prost implementate (difer mult ntre browsere, au sintax greoaie);

metoda E4X: o o avantaj: sintax foarte simpl, de tip JSON; dezavantaj: nesuportat de IE, incomplet suportat de alte browsere; dac tot dorim s folosim o sintax simplificat, lucrm de la bun nceput cu JSON.

Varianta client 1. Extragerea datelor n JavaScript cu standardul DOM

n pagina HTML, n funcia de procesare a rspunsului, folosii mesaje alert pentru a testa diverse metode de extragere a datelor din arborele DOM al rspunsului XML (testai alertele pe rnd, cte una, eu le scriu pe toate n aceeai funcie, cu explicaii sub form de comentarii):

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement alert(radacina.nodeName) //afiseaza numele radacinii - Comanda alert(radacina.firstChild.nodeName) //afiseaza numele primului fiu al radacinii - Produs alert(radacina.firstChild.nodeValue) //afiseaza valoarea primului fiu al radacinii - null!! //n standardul DOM continutul textual nu e considerat VALOARE, ci NOD fiu! alert(radacina.firstChild.firstChild.nodeValue) //abia acum se afiseaza textul primului element - Televizor alert(radacina.getAttribute('Client')) //afiseaza valoarea atributului radacinii - PopIon alert(radacina.getElementsByTagName('Produs')[0].childNodes[0].nodeValue) alert(radacina.getElementsByTagName('Produs')[1].childNodes[0].nodeValue) /*afiseaza continuturile textuale ale celor doua produse: Televizor, apoi Ipod; din nou, vedeti necesitatea de a accesa textul ca nod fiu ( cu firstChild sau, mai general, vectorul childNodes), cci n DOM textul nu e considerat valoare a elementului, ci nod fiu*/ alert(radacina.getElementsByTagName('Produs')[0].firstChild.nodeName) //afiseaza numele nodului text din primul Produs - #text, acesta e numele standard al nodurilor text alert(radacina.getElementsByTagName('Produs')[0].parentNode.nodeName) //afiseaza numele parintelui primului element Produs Comanda alert(radacina.childNodes[0].nextSibling.getAttribute('ID')) //afiseaza valoarea atributului ID pentru fratele urmator primului fiu al radacinii - P2

alert(radacina.childNodes.length) //afiseaza numarul de copii ai radacinii - 2 alert(radacina.firstChild.attributes.length) //afiseaza numarul de atribute al primului fiu al radacinii - 2 alert(radacina.firstChild.attributes[1].value) /*afiseaza valoarea celui de-al doilea atribut al primului fiu al radacinii - 100 deci e o alternativa la radacina.firstChild.getAttribute(), utila atunci cand nu cunoastem numele atributului*/ alert(radacina.firstChild.attributes[1].name) //afiseaza numele celui de-al doilea atribut al radacinii - Pret alert(radacina.hasAttributes()) //afiseaza true daca radacina are atribute - true alert(radacina.hasAttribute('Client')) //afiseaza true daca radacina are atributul Client - true alert(radacina.firstChild.firstChild.hasChildNodes()) //indica daca primul fiu al primului fiu al radacinii are proprii fii - false

alert(raspuns.nodeType) //afiseaza tipul raspunsului - 9 (cod pentru nodul document in ansamblul sau) alert(radacina.nodeType) //afiseaza tipul radacinii - 1 (cod pentru toate nodurile de tip element) alert(radacina.firstChild.firstChild.nodeType) //afiseaza tipul primului fiu al primului fiu al radacinii - 3 (nod de tip text) alert(radacina.firstChild.attributes[1].nodeType) //afiseaza tipul unui atribut - 2 (nod de tip atribut)

alert(radacina.firstChild.firstChild.length) //afiseaza lungimea continutului textual al primului fiu al radacinii - 9 alert(radacina.firstChild.firstChild.isElementContentWhitespace) //afiseza true la nodurile text care sunt invizibile; foarte utila la eliminarea lor! aici false

alert(radacina.getElementById('P1').firstChild.nodeValue) /*afiseaza continutul produsului cu ID P1 - null! standardul DOM nu recunoaste atributul ID, decat daca acesta e insotit de un vocabular ce il declara ca fiind de tip ID; cum obiectul XHR nu executa validare XML, nu putem folosi getElementById!!!*/ }

Obs:Spre deosebire de arborele DOM al paginii HTML, unde getElementById e foarte util, aici nu poate fi folosit datorit limitrii explicate n comentariu. Suntem nevoii s cutm IDul pe alte ci- nlocuii 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 ndemn, cu o funcie iteratoare ce detecteaz primul element (din vector) care respect o condiie

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 funcia-argument prin care trec toate elementele vectorului:

function testeaza(elem) { return elem.getAttribute('ID')=='P1' }

Obs importante: funcia $A(), oferit de Prototype, convertete un obiect ntr-un vector; poate prea redundant, cci tim c funcia getElementsByTagName returneaz vectori oricum, dar lucrul acesta e valabil doar pe arborele paginii (HTML DOM) nu i pe arborele rspunsului XML (aici funcia returneaz un obiect-colecie chiar dac putem s-l parcurgem cu FOR, nu putem s-l trecem printr-o funcie iteratoare pn nu facem o conversie explicit de la obiect la vector!). nu putem folosi optimizrile sintatice Prototype n locul funciilor DOM (nu va funciona $$() n locul lui getElementsByTagName, cnd e vorba de XML); chiar dac au acelai nume, funciile DOM se comport puin diferit cnd le executm asupra paginii HTML i cnd le executm asupra unui cod XML.

Rezumat: textele dintr-un cod XML nu sunt considerate valori ale elementelor ce le conin, ci valori ale nodurilor-fiu (de tip text) ale elementelor respective! getElementById nu se poate folosi pe un arbore DOM provenit din XML (dect dac e validat fa de un vocabular, operaie pe care XHR nu o asigur); funciile optimizate Prototype nu se pot folosi asupra arborelui DOM provenit din XML; n schimb funciile standard DOM pot fi folosite i asupra paginii HTML! (dar de obicei nu e nevoie, avem funciile Prototype sau JavaScript mai uor de folosit)

cteva instrumente eseniale: o o o o o o o o childNodes, firstChild, lastChild vectorul fiilor, primul i ultimul fiu; nodeValue, nodeName, nodeType valoarea, numele, tipul unui nod attributes vectorul atributelor; value sau getAttribute,name,nodeType valoarea, numele, tipul unu atribut length (aplicat lui childNodes sau attributes) numrul de noduri sau atribute parentNode acces la nodul printe; nextSibling,previousSibling acces la fratele urmtor sau precedent; hasAttribute, hasAttributes, hasChildNodes, isElementContentWhitespace teste de existen a componentelor unui nod.

Toate aceste exemple sunt funcii de ACCES (ele obin informaii de la arbore, dar nu l modific). Standardul DOM ofer i o gam larg de funcii de MANIPULARE (care modific structura arborelui DOM) acestea sunt importante acolo unde se construiete cod XML, ceea ce se ntmpl mai des pe server dect n JavaScript. Totui, destul de frecvent se face asta i n JavaScript, cnd dorim s construim cod HTML prin metode XML, ca alternativ la inserarea de coninut nou cu innerHTML!

Vom modifica exemplul astfel nct, n locul multiplelor alerte, s generm n pagin (n DIVul gol cu ID='x') un tabel HTML de forma:

Comanda lui PopIon Denumire Televizor Ipod Pret 100 30

Total:130

a. Inserarea datelor n pagin cu JavaScript clasic

nlocuim funcia de procesare a rspunsului cu urmtoarea:

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement //citeste raspunsul

client=radacina.getAttribute('Client') titlu="<h1>Comanda lui "+client+" </h1>" //creeaza titlul cu informatia luata din atributul radacinii XML

tabel="<table border='1'><tr><td>Denumire</td><td>Pret</td></tr>" total=0 //initializeaza codul HTML al tabelului (cu capul de tabel) si totalul cu zero

produse=radacina.getElementsByTagName('Produs') //selecteaza vectorul produselor

for (i=0;i<produse.length;i++) { tabel=tabel+"<tr><td>"+produse[i].firstChild.nodeValue+"</td>"

tabel=tabel+"<td>"+produse[i].getAttribute("Pret")+"</td></tr>" total=total+parseInt(produse[i].getAttribute("Pret")) } //pentru fiecare produs gasit, adauga un rand nou la codul HTML al tabelului si actualizeaza totalul

subsol="<tr><td colspan='2' align='right'>Total:"+total+"</td></tr></table>" //creeaza sfarsitul tabelului, cu totalul

locatie=document.getElementById("x") locatie.innerHTML=titlu+tabel+subsol //concateneaza toate fragmentele in proprietatea innerHTML a DIVului rezervat in acest scop }

Obs: parseInt converteste un string intr-un numar (avand in vedere ca valorile extrase din XML sunt implicit tratate ca stringuri); exemplul s-a bazat pe inserarea de continut nou cu innerHTML

b.Inserarea datelor n pagin nod cu nod (funcii DOM standard gzduite de JavaScript)

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement

client=radacina.getAttribute("Client")

titlu=document.createElement("h1") //s-a creat un element H1 in memorie text=document.createTextNode("Comanda lui"+client) //s-a creat un nod text (in memorie) titlu.appendChild(text) //s-a creat zona de titlu,lipind nodul text in interiorul nodului H1

tabel=document.createElement("table") tabel.setAttribute("border","1") captabel=document.createElement("tr")

celula=document.createElement("td") textcelula=document.createTextNode("Denumire") celula.appendChild(textcelula) captabel.appendChild(celula) //s-a creat prima celula in capul de tabel

celula=document.createElement("td") textcelula=document.createTextNode("Pret") celula.appendChild(textcelula) captabel.appendChild(celula) //s-a creat a doua celula in capul de tabel

tabel.appendChild(captabel) //s-a adaugat capul de tabel la tabel

total=0 produse=radacina.getElementsByTagName('Produs') for (i=0;i<produse.length;i++) { rand=document.createElement("tr") //s-a creat un rand gol

celula=document.createElement("td") text=document.createTextNode(produse[i].firstChild.nodeValue) celula.appendChild(text) rand.appendChild(celula) //s-a creat prima celula din rand

celula=document.createElement("td") text=document.createTextNode(produse[i].getAttribute("Pret")) celula.appendChild(text) rand.appendChild(celula) //s-a creat a doua celula din rand

tabel.appendChild(rand) //s-a adaugat randul la tabel

total=total+parseInt(produse[i].getAttribute("Pret")) }

subsol=document.createElement("tr") celula=document.createElement("td") celula.setAttribute("colspan","2") celula.setAttribute("align","right") text=document.createTextNode("Total:"+total) celula.appendChild(text) subsol.appendChild(celula) tabel.appendChild(subsol) //s-a creat si adaugat ultimul rand la tabel

locatie=document.getElementById("x") locatie.appendChild(titlu) locatie.appendChild(tabel) //se insereaza in DIV titlul, apoi tabelul }

Obs importante:

Se observ c adugarea de coninut nod cu nod e mai anevoioas dect inserarea cu innerHTML. Totui e important din 2 motive: innerHTML putem folosi doar cnd: o o avem un loc rezervat n pagin (un DIV gol) gata s primeasc noul coninut; nou coninut e disponibil sau poate fi creat (prin concatenri) sub form de string;

cnd nu exist un nod gol rezervat iar coninutul nu e disponibil ca string, avem variantele:

s facem inserare ca ultim fiu (ne poziionm pe printele locaiei n care inserm i folosim appendChild); s facem inserare ca frate precedent (ne poziionm pe fratele naintea cruia vrem s inserm i folosim insertBefore); folosim Prototype care ne ofer Element.insert ce permite poziionare ca prim copil, ca ultim copil, ca frate precedent sau ca frate urmtor (n funcie de cum reuim s ne poziionm).

Metoda cu funcii DOM standard este mai des folosit la nivelul serverului (PHP) unde se construiesc adesea rspunsuri XML n aceast manier.

Am vzut la nceput funciile de ACCESARE (citire) a informaiilor din arbore. Acum observai funciile de MANIPULARE (scriere): createElement, createTextNode (apelate de obiectul document dac inserm coninut n pagina HTML sau de la nivelul rdcinii dac inserm coninut n cod XML); o nceptorii fac adesea gafa de a crede c aceste funci asigur i alipirea nodurilor la document! alipirea se face cu funciile 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 coninutului; 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 funci pot returna nodul creat/inserat/modificat/sters (dac vrem s-i aducem ulterioare modificri)! De exemplu dac vrem s mutm un nod dintr-o poziie n alta ne putem baza c removeChild ni-l returneaz dup ce l terge din poziia original, deci l putem reinsera uor n alt poziie (fr s-l mai crem o dat).

Aceste funcii nu pot fi combinate cu innerHTML! innerHTML funcioneaz:

DOAR pe elemente care au fost deja alipite la coninutul paginii (indiferent cum); DOAR pentru a insera coninut stocat ca string.

c.Inserarea datelor n pagin cu faciliti Prototype

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement

client=radacina.getAttribute('Client') titlu=new Element("h1") titlu.update("Comanda lui"+client) //crearea titlului

tabel=new Element("table",{border:1}) rand=new Element("tr") celula=new Element("td") celula.update("Denumire") rand.insert(celula) celula=new Element("td") celula.update("Pret") rand.insert(celula) tabel.insert(rand) //crearea capului de tabel

total=0 produse=radacina.getElementsByTagName('Produs') vectproduse=$A(produse) vectproduse.each(genereaza) //atasarea unei functii iteratoare care genereaza randurile tabelului

subsol=new Element("tr") celula=new Element("td",{align:"right",colspan:2}) celula.update("Total:"+total) tabel.insert(celula) //crearea subsolului

$('x').insert(titlu) $('x').insert(tabel) //inserarea in DIV } //in continuare, urmeaza functia iteratoare care genereaza randurile tabelului si calculeaza totalul function genereaza(elem) { rand=new Element("tr") celula=new Element("td") celula.update(elem.firstChild.nodeValue) rand.insert(celula) celula=new Element("td")

celula.update(elem.getAttribute("Pret")) rand.insert(celula) tabel.insert(rand) total=total+parseInt(elem.getAttribute("Pret")) }

Obs.: funciile update (substituire de coninut) i insert (adugare) pot fi folosite pe noduri XML la fel ca pe noduri HTML (ca alternativ la appendChild/insertBefore)! cele dou funcii pot fi apelate n oricare din formele: o o o Element.update(int,coninut-nou) int.update(coninut-nou) $('id-int').update(coninut-nou)

new Element() e o alternativ la document.createElement() are avantajul c ne permite s definim atributele chiar la momentul crerii (nu mai trebuie apelat setAttribute) dup cum s-a vzut deja, funcia $A e necesar pentru a face ca vectorul rezultat din getElementsByTagName s poat fi procesat de Prototype (n acest caz cu o funcie iteratoare).

Varianta client 2. Extragerea cu XSLT

O alt metod de a insera datele rspunsului XML n pagina Web e s aplicm o transformare XSLT la nivelul browserului. Toate browserele moderne suport transformri XSLT 1.0 (bazate pe interogri 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 lucrm pe Prototype). Deci vom avea dou comunicri asincrone una care aduce rspunsul XML i una care aduce foaia XSLT. Odat ajunse ambele n browser, executm transformarea.

Pas1. Salvm urmtoarea foaie XSLT pe server (n htdocs) cu numele transformare.xsl:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <h1>Comanda lui <xsl:value-of select="Comanda/@Client"/></h1> <table border="1"> <tr> <td>Denumire</td> <td>Pret</td> </tr> <xsl:for-each select="Comanda/Produs"> <tr> <td><xsl:value-of select="."/></td> <td><xsl:value-of select="@Pret"/></td> </tr> </xsl:for-each> <tr> <td colspan="2" align="left"> Total: <xsl:value-of select="sum(Comanda/Produs/@Pret)"/> </td> </tr> </table> </xsl:template> </xsl:stylesheet>

Pas2. Modificm scriptul xml.php: dac primete prin GET variabila "foaie", deschide de pe disc foaia XSLT i o livreaz browserului. Dac nu, livreaz codul XML din exemplele precedente, luat din acelai string (nu dai Enteruri n el):

<?php header('Content-type:application/xml'); if (isset(GET["foaie"])

{ $foaie=new DOMDocument(); $foaie->load('transformare.xsl'); print $foaie->saveXML(); } else { $sirxml='<?xml version="1.0" encoding="UTF-8"?><Comanda Client="PopIon"><Produs ID="P1" Pret="100">Televizor</Produs><Produs ID="P2" Pret="30">Ipod</Produs></Comanda>'; print $sirxml; } ?>

Pas3. Scriem pagina client (scriei una nou, cci vom reveni la cea precedent):

<!DOCTYPE html> <html> <head> <script src="scriptaculous/lib/prototype.js"></script> <script> function cheamaxml() { config={method:"GET",onSuccess:cheamaxslt} new Ajax.Request("xml.php",config) } /*

S-a solicitat raspunsul XML de la xml.php. La sosirea raspunsului se apeleaza functia cheamaxslt, pentru a aduce si foaia XSLT */ function cheamaxslt(xhr) { raspunsxml=xhr.responseXML config={method:"GET",parameters:"foaie",onSuccess:proceseaza} new Ajax.Request("xml.php",config) } /* S-a solicitat foaia XSLT, folosind variabila foaie, fara valoare - e suficient ca e trimisa ( caci scriptul PHP testeaza doar existenta ei, cu if (isset($_GET['foaie'])) ) La sosirea foii se apeleaza functia proceseaza, responsabila cu executia transformarii. */ function proceseaza(xhr) { raspunsxslt=xhr.responseXML if (Prototype.Browser.IE) { rezultat=raspunsxml.transformNode(raspunsxslt) $("x").update(rezultat) } // aceasta a fost varianta pentru IE else { procesorxslt=new XSLTProcessor()

procesorxslt.importStylesheet(raspunsxslt) rezultat=procesorxslt.transformToFragment(raspunsxml,document) $("x").appendChild(rezultat) } // aceasta a fost varianta pentru FF }

</script> </head> <body> <div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()"> Treci cu mouseul pe aici pentru a chema documentul XML de la server </div> <div id="x">Aici se va insera informatia din XML</div> </body> </html>

Obs: observai modul n care am nlnuit cele 2 Ajax.Request, cu ajutorul evenimentului onSuccess: cnd termin de venit rspunsul XML, se iniiaz solicitarea pt foaia XSLT, iar cnd termin de venit i aceasta, se apeleaz procedura de transformare; o n acest fel ne asigurm c transformarea nu e executat nainte s fi ajuns ambele componente la browser i c nu exist riscul unui conflict ntre cele 2 comunicri asincrone;

deoarece avem Prototype, am folosit Prototype.Browser.IE pentru browser sniffing, pentru a detecta dac ne aflm n IE: o dac nu lucrm 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 funcia update pentru a insera rezultatul transformrii n pagin; o aceasta deoarece n IE transformarea returneaz text! (deci se poate folosi i innerHTML, dac nu avem Prototype);

n varianta FF nu merge cu update sau innerHTML, trebuie s inserm 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 privete limbajul Xpath) dar practic e destul de complicat (datorit funciilor JavaScript care gzduiesc interogrile Xpath i le gestioneaz rezultatele). n plus, sunt diferene mari ntre browsere n ce privete aceste funcii.

Revenii la pagina client aa cum arta nainte de exemplul cu XSLT (cu un singur Ajax.Request). Scriptul PHP poate sa rmn cel cu ultimele modificari, cci conine un IF care ne va da codul XML iniial cu condiia s nu trimitem variabila GET "foaie").

Vom rescrie funcia proceseaza() pentru a extrage diverse informaii din XML cu XPath.

Urmtoarele exemple NU vor funciona n Internet Explorer:

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement rez=raspuns.evaluate('count(Produs)',radacina,null,XPathResult.ANY_TYPE,null) /*detalii:

- functia trebuie apelata de catre documentul pe care se face interogarea (aici, raspuns); - primul argument e interogarea efectiva (relativa, de obicei); - al doilea argument e nodul curent (fata de care se calculeaza calea relativa); - al treilea argument e mai putin important (pentru XML cu prefixe/spatii de nume); - al patrulea argument e un cod ce indica tipul dorit pt rezultat (XPathResult.ANY_TYPE va returna: - tipul numeric pentru functii precum count si sum - tipul string pentru functii ce returneaza stringuri - tipul boolean pentru functii precum boolean sau not - tipul iterator pentru interogari ce returneaza noduri sau colectii de noduri (chiar daca e returnat un singur nod sau atribut!) - ultimul argument e neimportant (e variabila in care sa se stocheze rezultatul, dar in cazul de fata indicam asta prin atribuire) - odata ce s-a obtinut un rezultat, valoarea acestuia se extrage prin diferite metode in functie de tipul rezultatului */ alert("tipul rezultatului este "+rez.resultType) /*va afisa 1, codul corespunzator tipului numeric; tipul numeric e returnat de: - functii XPath precum count sau sum (dac am pus argumentul ANY_TYPE) - orice interogri 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 interogri ce returneaz valori/noduri textuale (dac am pus argumentul STRING_TYPE) */ alert("atributul cu valoarea 100 se numeste "+rez.stringValue) //va afisa valoarea string a rezultatului - Pret (numele atributului cu valoarea 100)

rez=raspuns.evaluate('Produs="Televizor"',radacina,null,XPathResult.ANY_TYPE,null) alert("tipul rezultatului este "+rez.resultType) /*va afisa 3, codul corespunzator tipului boolean; tipul boolean e returnat de: - functii XPath ce returneaz valori booleene (dac am pus argumentul ANY_TYPE) - orice interogri ce returneaz mcar o soluie (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 funcii XPath), chiar dac s-a gsit o singur soluie: - dup returnare, rezultatul se parcurge cu un ciclu FOR sau WHILE, care apeleaza in mod repetat functia iterateNext() ce returneaza urmatorul nod din setul rezultat */ produs=rez.iterateNext()

while (produs) { alert("S-a gasit produsul cu pretul "+produs.getAttribute("Pret")+ " si continutul "+produs.firstChild.nodeValue) produs=rez.iterateNext() } alert("S-au terminat produsele")

rez=raspuns.evaluate('..',radacina.firstChild.firstChild,null,XPathResult.ANY_TYPE,null) alert("tipul rezultatului este "+rez.resultType) /*va afisa 4, din nou rezultat iterator - in acest caz am folosit ca nod curent primul nepot al radacinii, iar prin interogare am solicitat parintele sau */ alert(rez.iterateNext().nodeName) //va afisa Produs, numele parintelui primului nepot din radacina

rez=document.evaluate('div[@id]',document.body,null,XPathResult.ANY_TYPE,null) alert("tipul rezultatului este "+rez.resultType) /*acest exemplu demonstreaza ca XPAth se poate folosi si asupra paginii HTML! diferene: - funcia 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 coninutul din DIVul care are ID ("Aici se va insera informatia din XML") }

Pentru Internet Explorer implementarea e mult mai simpl. Diferenele principale sunt: se folosesc dou funcii: selectNodes (pentru interogri care returneaz mai multe noduri) sau selectSingleNode (pentru interogri care returneaz un nod); ambele funcii au un singur argument - interogarea XPath; ambele funcii returneaz noduri XML (nu trebuie s indicm tipul rezultatului); ambele funcii folosesc ca nod curent obiectul care le apeleaz (nu trebuie s indicm nodul curent); limitri: o nu se pot executa interogri ce returneaz valori calculate (cu funcii XPath) ci numai interogri ce returneaz noduri/valori de noduri; nu se pot interoga elemente din pagina HTML, cu numai din rspunsul XML.

function proceseaza(xhr) {

raspuns=xhr.responseXML radacina=raspuns.documentElement raspuns.setProperty("SelectionLanguage","XPath") /*aceasta linie e necesara, altfel IE va numerota nodurile incepand de la zero (iar XPath le numara de la 1!)*/

rez=radacina.selectNodes("Produs") for (i=0;i<rez.length;i++) alert("S-a gasit produsul cu pretul "+rez[i].getAttribute("Pret")+ " si continutul "+rez[i].firstChild.nodeValue) //s-au returnat toate produsele i s-au parcurs afind 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 interogrile XPath sunt mai rapide dect accesarea cu funcii DOM standard (getElement.). De fapt numeroase frameworkuri AJAX (inclusiv Prototype) apeleaz, n implementarea unor funcii precum $, $$, la XPath i nu la funciile getElement, ceea ce face ca funciile Prototype, dei mult mai puternice, s aib performane asemntoare cu getElementsByTagName). Totui, dpdv sintactic, XPath nc nu e o soluie larg adoptat diferenele mari ntre browsere (ce necesit dublarea codului surs) i complicaiile inutile ale sintaxei din Firefox au descurajat n general adopia acestei tehnici ca alternativ la funciile standard DOM.

Varianta client 5.Extragerea cu E4X

Varianta E4X e o sintax JavaScript mai uor de folosit dect funciile DOM:

radacina.Produs[0] n loc de

radacina.firstChild.firstChild.nodeValue

radacina.Produs.@Pret n loc de radacina.firstChild.getAttribute('Pret')

Nu vom insista totui 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 cilor Xpath (cu condiia 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 rspunsul 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 fiier

Ultimele exemple au prezentat diferite moduri de procesare a rspunsului XML dup ce acesta a ajuns la browser. n continuare mutm discuia 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 fiier existent pe server. Construii urmtorul fiier cu Oxygen, i salvai-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: ncrcm fiierul ntr-un obiect de tip XML (clas numit DOMDocument n PHP); de obicei fiierele create prin tastare conin noduri invizibile (Enterurile, Taburile, spaiile folosite n timpul tastrii pentru aranjarea codului vor fi considerate noduri text).

Prezena nodurilor invizibile va afecta traversarea pe baz de frai (nextSibling, previousSibling) sau pe baz de poziie (childNodes,firstChild,lastChild).

Important: Internet Explorer elimin automat nodurile invizibile, dar alte browsere nu!

Construim scriptul PHP (salvai-l cu numele xml2.php) care deschide acest fiier i l trimite la browser:

<?php header('Content-type:application/xml'); $cmdxml=new DOMDocument(); $cmdxml->load('comanda.xml'); print $cmdxml->saveXML(); ?>

Obs: - Se pot ncrca i stringuri n obiectul XML, dar cu loadXML n loc de load

Construim pagina client, similar cu cea din primele exemple (dar acum solicit rspuns de la xml2.php), i punem cteva alerte pentru a ne convinge de prezena cmpurilor invizibile:

<!DOCTYPE html> <html> <head> <script src="scriptaculous/lib/prototype.js"></script> <script> function cheamaxml() { config={method:"GET",onSuccess:proceseaza} new Ajax.Request("xml2.php",config) }

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement alert(radacina.firstChild.nodeName) //nu mai apare Produs, ci #text (numele standard al nodurilor text)! alert(radacina.firstChild.nodeValue) //apare valoarea invizibila a nodului alert(radacina.childNodes.length) //nu mai apare 2, ci 5 - inclusiv noduri invizibile alert(radacina.getElementsByTagName('Produs')[0].nextSibling.nodeName) //urmatorul frate al primului produs nu e tot un produs, ci un nod invizibil alert(radacina.firstChild.isElementContentWhitespace)

//testeaza daca primul fiu al radacinii e un nod invizibil - true! } </script> </head> <body> <div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamaxml()"> Treci cu mouseul pe aici pentru a chema documentul XML de la server </div> <div id="x">Aici se va insera informatia din XML</div> </body> </html>

Obs: Se poate observa c n Internet Explorer, aceleai alerte dau alte rezultate (IE ignor nodurile invizibile!)

Pentru cazurile n care nodurile invizibile ne-ar putea influena, se practic 2 abordri: se creeaz o funcie recursiv ce parcurge tot arborele DOM i elimin nodurile invizibile detectate; se creeaz o foaie XSLT care transform arborele DOM ntr-unul echivalent, dar fr noduri invizibile.

Ambele strategii se pot aplica att la client (JavaScript), ct i la server (PHP). Adesea e preferabil s aib loc n PHP, pentru a nu se transfera degeaba octei n plus, care oricum trebuie eliminai.

Vom exemplifica funcia recursiv n JavaScript i transformarea n PHP. Exemplele se pot adapta la orice limbaj cu modificri minime (date de specificul sintactic).

Funcia recursiv de eliminare a nodurilor invizibile n JavaScript:

function eliminaNoduriInv(nod) { for (var i=0;i<nod.childNodes.length;i++) { var fiu=nod.childNodes[i] if (fiu.isElementContentWhitespace) //dac fiul e nod invizibil, e ters { nod.removeChild(fiu) i-} if (fiu.nodeType==1) eliminaNoduriInv(fiu) } return nod } //dac fiul e un element, i se aplic recursiv curarea //contorul trebuie decrementat, caci stergerea a afectat numarul de fii! //se parcurg toi fiii

Obs: funcia primete ca argument orice nod i returneaz acelai nod, curat de noduri invizibile; e important s apar cuvntul var! n programarea recursiv din JavaScript trebuie s-l punem datorit particularitii JavaScript c funciile i pot refolosi variabilele rmase de la apelul precedent o asta va avea efectul neplcut c apelul recursiv va reutiliza contorul i de la apelul precedent (primul ciclu FOR) i obinem ciclu infinit cci contorul va fi mereu resetat la 1!

prezena 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 funcia 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 mcar un caracter diferit de cele invizibile (e vorba de o expresie regulat cu codul \S ce reprezint orice caracter diferit de cele invizibile);

Pentru a testa succesul acestei funcii, vom afia codul XML nainte i dup transformare. Vom profita de ocazie pentru a arta i cum se poate face conversia din XML n string, n JavaScript. i aceast operaie are diferene mari ntre browsere:

function proceseaza(xhr) { raspuns=xhr.responseXML radacina=raspuns.documentElement

if (Prototype.Browser.IE) { sirxml=radacina.xml alert(sirxml) } /*daca ne aflam in IE nu are sens sa aplicam curatarea (totusi, in mesaje alert, codul XML arata ca si cum n-ar fi fost curatat!)*/ else { serializator=new XMLSerializer() sirxml=serializator.serializeToString(radacina)

alert("Inainte de curatare:\n "+sirxml) radacinacurata=eliminaNoduriInv(radacina) sirxml=serializator.serializeToString(radacinacurata) alert("Dupa curatare:\n "+sirxml) } } /*daca ne aflam in FF, afisam codul XML inainte si dupa curatare*/

Obs: din nou, am detectat browserul cu Prototype (reminder: n lipsa lui Prototype, testarea existenei obiectului document.all duce la acelai rezultat); IE afieaz codul XML ca i cum n-ar fi fost curat (dar v putei convinge c a fost, numrnd/afind nodurile din document prin alerte cu informaii extrase din nodul DOM); o conversia XML-string n IE e asigurat de proprietatea xml;

FF l afieaz diferit nainte de curate i dup curare: o conversia XML-string n FF e asigurat de clasa XMLSerializer

Varianta server 3. Codul XML e obinut printr-o transformare XSLT pe server

Exemplificm transformarea XSLT n PHP tot prin eliminarea de noduri invizibile:

Presupunem c avem pe server fiierul comanda.xml cu coninutul:

<?xml version="1.0" encoding="UTF-8"?> <Comanda Client="PopIon"> <Produs ID="P1" Pret="100">Televizor</Produs>

<Produs ID="P2" Pret="30">Ipod</Produs> </Comanda>

Avem i foaia XSLT care elimin noduri invizibile, salvat cu transformare2.xml:

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:strip-space elements="*"/> <xsl:template match="/"> <xsl:copy-of select="."/> </xsl:template> </xsl:stylesheet>

Scriptul PHP va fi:

<?php header('Content-type:application/xml'); $sursa=new DOMDocument(); $sursa->load('comanda.xml');

$foaie=new DOMDocument(); $foaie->load('transformare2.xsl');

$procesor=new XSLTProcessor(); $procesor->importStylesheet($foaie); $rezultat=$procesor->transformToXML($sursa);

print $rezultat; ?>

Obs: instrumentele sunt aceleai cu cele pe care le-am folosit n JavaScript pentru a executa transformarea sub Firefox: o o o clasa XSLTProcessor; metodele importStylesheet i transformToXML (ambele apelate de procesorul XSLT); rezultatul e text i poate fi returnat imediat cu print (fr saveXML).

Pentru a testa succesul operaiei: fie cuplai pagina client la acest script i afiai modul n care ajunge rspunsul acestui script n Firefox (dup cum am vzut, n IE nu se vede diferena n alerte); fie accesai direct acest script din localhost i v uitai cum arat rspunsul n Firebug (n browser codul XML va fi afiat formatat, doar n Firebug, la rubrica Net putei vedea efectiv irul de caractere din care lipsesc nodurile invizibile).

Evident, n aceeai manier putem executa n PHP transformarea ce producea tabelul HTML.

Varianta server 4. Codul XML e generat pe server, nod cu nod

n sfrit, codul XML poate fi generat pe server i cu funciile 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:

funciile DOM standard (clasa DOMDocument); un set simplificat de funcii (clasa SimpleXMLElement).

Urmtoarele exemple testai-le direct n browser (http://localhost/fisier.php), fr a mai executa pagina client (doar ct s v convingei cum arat documentul XML generat):

Metoda standard, cu Clasa DOMDocument:

<?php header("Content-type:application/xml");

$raspuns=new DOMDocument(); $radacina=$raspuns->createElement("Comanda"); $radacina->setAttribute("Client","PopIon");

$produs=$raspuns->createElement("Produs"); $produs->setAttribute("ID","P1"); $produs->setAttribute("Pret","100"); $text=$raspuns->createTextNode("Televizor"); $produs->appendChild($text); $radacina->appendChild($produs);

$produs=$raspuns->createElement("Produs"); $produs->setAttribute("ID","P2"); $produs->setAttribute("Pret","30"); $text=$raspuns->createTextNode("Ipod");

$produs->appendChild($text); $radacina->appendChild($produs);

$raspuns->appendChild($radacina);

print $raspuns->saveXML(); ?>

Observai linia boldat, prin care elementul rdcin e inserat n nodul-document (rdcina 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();

?>

Comparai codul surs i observai economia de sintax (aproape njumtit): nu mai trebuie s lipim elementul rdcin la document (funcia asXML() de la final se ocup de asta implicit); crearea i alipirea unui nou element cu coninut 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 coninut n primul produs, ca alternativ la metoda standard: $radacina->getElementsByTagName("Produs")[0]->firstChild->nodeValuesimplexmlelement

Varianta server 5. Rspunsul 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 posibiliti avansate precum transformarea XSLT, interogarea datelor, validarea cu vocabulare). n PHP, codul JSON se poate crea uor din orice array existent. Salvai urmtorul 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); ?>

Creai pagina client care solicit acest cod i genereaz un tabel cu denumirile i preurile produselor:

<!DOCTYPE html> <html> <head> <script src="scriptaculous/lib/prototype.js" type="text/javascript"></script> <script> function cheamajson() { config={method:"GET",onSuccess:proceseaza} new Ajax.Request("json.php",config) } function proceseaza(xhr) { eval("raspuns="+xhr.responseText) client=raspuns.Comanda.Client titlu="<h1>Comanda lui "+client+" </h1>"

tabel="<table border='1'><tr><td>Denumire</td><td>Pret</td></tr>" total=0 produse=raspuns.Comanda.Produse for (i=0;i<produse.length;i++) { tabel=tabel+"<tr><td>"+produse[i].Denumire+"</td>" tabel=tabel+"<td>"+produse[i].Pret+"</td></tr>" total=total+parseInt(produse[i].Pret) } subsol="<tr><td colspan='2' align='right'>Total:"+total+"</td></tr></table>" locatie=document.getElementById("x") locatie.innerHTML=titlu+tabel+subsol } </script> </head> <body> <div style="width:200px;height:200px;border:2px solid red" onmouseover="cheamajson()"> Treci cu mouseul pe aici pentru a chema raspunsul JSON de la server </div> <div id="x">Aici se va insera informatia din JSON</div> </body></html>

Obs: funcia eval asigur conversia rspunsului JSON din text n obiect JavaScript; n practic e recomandat ca n loc de eval s se foloseasc o bibliotec de funcii JSON precum https://github.com/douglascrockford/JSON-js, care are n plus un mecanism de securitate ce

verific dac nu cumva n rspunsul JSON exist i comenzi JavaScript (care s-ar executa automat de ctre eval); observai 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 msurate att n Firebug (numrul de bii i timpul de transfer comparativ cu XML) sau prin cronometrarea n JavaScript a procedurii de generare a tabelului (n cazul cronometrrii avantajele nu vor fi semnificative pe aceste exemple datorit numrului mic de date transferate/interogate).