venerdì 30 novembre 2007

Firebug: Plugin Firefox per Sviluppatori

Firebug è un plugin per Mozilla Firefox che mette a disposizione una serie di strumenti utili per disegnatori di siti web e sviluppatori.

La documentazione ufficiale è piuttosto scarna per cui ho pensato fosse utile spendere alcune parole su come utilizzarlo.

Nota preliminare: se le immagini non sono chiare cliccatele per visualizzarle a pieno schermo

Una volta installato il plugin crea una voce Firebug all'interno del menu Strumenti di Firefox. Selezionando Open Firebug il plugin apre una finestra nella parte bassa dello schermo.

Visualizzazione html e css

La finestra è divisa in due parti. La parte sinistra mostra il codice html della pagina web con evidenziazione in diversi colori della sintassi dei tag e attributi e una corretta indentazione dei vari blocchi per migliorare la leggibilità del codice. Agendo sulle icone con i segni + e - a fianco di ciascun tag di apertura è possibile espandere o collassare il blocco relativo. In questo modo è facile mettere in evidenza soltanto la parte di codice su cui si vuole lavorare.

Muovendo il puntatore al di sopra di un tag html, la parte di pagina web generata da quel blocco di codice viene evidenziata in un diverso colore.

Si può anche seguire il percorso inverso, partire cioè dalla pagina web visualizzata per arrivare al codice. Basta premere il pulsante Inspect sulla barra strumenti di Firebug e muovere il puntatore del mouse sulla porzione visibile della pagina web. Una volta posizionatisi sulla parte che ci interessa, (un bordo viene disegnato intorno ai vari blocchi per facilitarci il posizionamento) basta cliccare e il codice corrispondente a quella parte di pagina verrà visualizzato nella finestra del plugin.

In questo modo è molto facile orientarsi nel codice e questo è uno dei motivi per cui l'uso di Firebug è estremamente intuitivo.

Restando sulla finestra di visualizzazione del codice html, altre funzionalità del plugin sono accessibili premendo il pulsante destro del mouse. Vediamo le più importanti

  • New Attribute. Aggiunge un attributo al tag html su cui è posizionato il puntatore del mouse.
  • Edit. Apre una finestra di editing dove è possibile modificare il tag html corrente, i suoi attributi e il contenuto, ad esempio il testo racchiuso in un tag div o paragrafo.
  • Delete Element. Rimuove il tag corrente dal documento.

L'effetto di tutte le modifiche apportate mediante queste funzioni si riflette in tempo reale sull'aspetto della pagina web visualizzata. Naturalmente ciò che viene modificato è solo la versione del documento nella memoria del browser, una volta soddisfatti del risultato occorre aprire il documento html con un editor e riportarvi le modifiche o queste verranno perse quando la pagina viene ricaricata.

Visualizzazione dei fogli di stile CSS

La parte destra della finestra del plugin mostra i fogli di stile CSS associati alla pagina web visualizzata. Le due viste, html e css sono automaticamente sincronizzate tra loro. Se si seleziona con il mouse un tag html nella finestra del codice, la finestra CSS mostra le proprietà impostate nel foglio di stile ed applicabili a quel tag.

Da notare che tutte le proprietà impostate in un selettore applicabile al tag html selezionato sono visualizzate in cascata. Un esempio è necessario.

Supponiamo di aver creato un foglio di stile con alcune proprietà in un selettore per tutti i paragrafi del documento e altre in un selettore specifico per i paragrafi di una certa classe. In questo modo.

p {
  margin: 15px;
  font-size: 10pt;
}

p.small {
  font-size: 8pt;
}

Una volta assegnato questo foglio di stile ad un documento, il codice viene mostrato da Firebug come nella immagine sottostante.

Quando il tag del paragrafo di classe small è selezionato nella vista del codice html, si può notare come la vista css riporti sia il selettore paragrafo sia il selettore più specifico per i paragrafi di classe small. La proprietà font-size all'interno del primo selettore è sbarrata ad indicare che è stata sovrascritta dalla proprietà impostata nel selettore più specifico.

La possibilità di individuare a colpo d'occhio quali proprietà sono applicabili alla parte di codice html in esame e in quali selettori sono impostate è utilissima quando si vuole modificare l'aspetto di pagine web con fogli di stile complessi.

Firebug rende molto semplice vedere l'effetto che avrebbe la rimozione di una determinata proprietà da un selettore. Se posizionamo il puntatore del mouse a fianco della proprietà font-size nel selettore p.small e premiamo il pulsante sinistro compare il simbolo () e la proprietà viene disabilitata. Allo stesso tempo la proprietà font-size nel selettore p non è più sbarrata e la pagina web si aggiorna per riflettere la nuova dimensione di font assegnata al paragrafo.

Sempre restando all'interno della vista css sono disponibili ulteriori funzioni

  • Modifica di una proprietà. Facendo click sul valore di una proprietà si apre un piccolo campo di testo dove si può impostare il nuovo valore.
  • Inserimento di nuova proprietà. Fare click con il pulsante destro all'interno del selettore e selezionare New Property.
  • Cancellazione di una proprietà. Fare click con il pulsante destro sul nome o valore della proprietà e selezionare Delete.

Vale anche in questo caso quanto detto per la modifica del sorgente html della pagina. Modificare il css tramite Firebug è utile per vedere in tempo reale l'effetto delle modifiche sull'aspetto del documento quando è visualizzato nel browser, ma il plugin in sè non è un editor, una volta che si è soddisfatti del risultato si dovrà editare il file css e riportarvi le modifiche effettuate.

Debugging di script Javascript

Premendo il pulsante Script sulla barra strumenti si visualizza il sorgente di tutti gli script Javascript attivi sulla pagina corrente sia quelli incorporati nel sorgente che quelli richiamati da file esterni.

Facendo click su un numero di riga del sorgente si ha la possibilità di impostare dei punti di interruzione. Quando si ricarica la pagina l'esecuzione dello script si ferma al primo punto di interruzione. Si può quindi continuare l'esecuzione passo passo o far proseguire lo script fino al successivo punto di interruzione. La parte destra dello schermo (finestra Watch) mostra i valori aggiornati di tutte le variabili, inclusi gli oggetti, dichiarate nel codice.

Non abbiamo esaurito tutte le funzioni di Firebug, ma le rimanenti possono essere facilmente apprese installando il plugin e facendo qualche esperimento.

Firebug è un ottimo prodotto. Il debugger Javascript a volte risulta un po' instabile e può provocare il blocco di Firefox, le funzioni per esplorare html e css sono comunque, anche da sole, una ragione più che sufficiente per installare questo plugin.

martedì 27 novembre 2007

Javascript: processare documenti XML

Javascript mette a disposizione una serie di oggetti e funzioni per processare e manipolare documenti XML. Possono tornare utili in diversi casi per cui penso sia interessante vedere alcuni esempi di codice.

Il documento XML che utilizzeremo negli esempi è compositori.xml (se cliccate il link dovreste visualizzarlo nel vostro browser).

Non è il caso di fare qui troppa teoria su XML, basterà dire quanto basta per comprendere il codice.

Un documento XML è organizzato gerarchicamente in una struttura ad albero costituita da un nodo radice che a sua volta contiene tutti gli altri nodi. Ogni nodo, ad eccezione della radice, ha un nodo padre e può contenere altri nodi, detti figli. I nodi con lo stesso nodo padre sono detti fratelli, mentre i figli dei figli di un nodo (così come i figli dei figli dei figli per potenzialmente infinite generazioni) sono detti discendenti del nodo. A ciascun nodo inoltre possono essere associate delle proprietà (attributi).

Parser XML

Prima di poter compiere una qualsiasi operazione su file XML è necessario ottenere un'istanza del parser XML. Per fare questo, come spesso succede, si è costretti a scrivere codice Javascript differenziato tra Internet Explorer e resto del mondo.

1) Internet Explorer

var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");

2) Firefox, Mozilla, Opera e altri

var xmlDoc=document.implementation.createDocument("","",null);

Ottenuta una istanza del parser possiamo caricare il documento in questo modo

xmlDoc.async=false;
xmlDoc.load("compositori.xml");

Impostando async a false ci assicuriamo che lo script attenda il completo caricamento del documento XML prima di proseguire l'esecuzione.

Radice (root) del documento XML

L'elemento radice del documento può essere sempre ottenuto con

var root = xmlDoc.documentElement;

Tipi di nodo

L'oggetto radice, come tutti gli altri nodi, possiede tre proprietà fondamentali: nodeType, nodeName, nodeValue.

Se, facendo riferimento al nostro documento, scriviamo

document.write('Nome: ' + root.nodeName);
document.write('<br />');
document.write('Tipo: ' + root.nodeType);
document.write('<br />');
document.write('Valore: ' + root.nodeValue);

Otteniamo il seguente output

Nome: compositori
Tipo:1
Valore: null

che ci dice che il nodo radice del documento ha nome compositori ed è di tipo Elemento. La proprietà nodeType può avere infatti i seguenti valori

  • 1 - Elemento
  • 2 - Attributo
  • 3 - Testo
  • 8 - Commento
  • 9 - Documento

Il nodo non ha valore (nodeValue è null) come tutti i nodi di tipo Elemento.

Proprietà childNodes

La proprietà childNodes di un nodo di tipo Elemento restituisce la lista di tutti i sottoelementi o nodi figli.

Scorriamo i figli del nodo radice compositori

for (var i=0; i < root.childNodes.length;  i++)  {
  document.write('Nome: ' + root.childNodes[i].nodeName);
  document.write('<br />');
  document.write('Tipo: ' + root.childNodes[i].nodeType);
  document.write('<br />');
  document.write('Valore: ' + root.childNodes[i].nodeValue);
  document.write('<br /><br />');
}

Se eseguiamo il codice si ottiene

Nome: compositore
Tipo: 1
Valore: null

Nome: compositore
Tipo: 1
Valore: null

Nome: compositore
Tipo: 1
Valore: null

Infatti il nodo radice (compositori) ha tre nodi figli (compositore) tutti di tipo Elemento, basta un'occhiata alla struttura del documento per capire che tutto torna. C'è solo un problema. Quanto riportato sopra si ottiene eseguendo il codice in Internet Explorer. Con Firefox e gli altri browser lo stesso codice genera il seguente output

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore:

Nome: compositore
Tipo: 1
Valore: null

Nome: #text
Tipo: 3
Valore: 

Da dove vengono quei 4 nodi di tipo Testo in più? Sono gli a capo e i caratteri di tabulazione usati per separare e spaziare i tag relativi ai nodi figlio (<compositore>) rispetto al tag del nodo padre (<compositori>). In Internet Explorer vengono scartati automaticamente, negli altri browser vengono considerati nodi figlio di tipo Testo.

Un modo per ovviare al problema, e uniformare i risultati tra i diversi browser quando si utilizza Javascript per processare documenti XML, è scriversi una funzione personalizzata che, dato un nodo, ne restituisca i nodi figli di tipo Elemento escludendo gli altri tipi. La funzione potrebbe essere questa.

function childElements(node) {
  var elements = new Array();
  for (var i=0; i < node.childNodes.length;  i++)  {
    if(node.childNodes[i].nodeType == 1) {
      elements.push(node.childNodes[i]);
    }
  }
  return elements;
}

Proprietà nextSibling e previousSibling

Nodi con lo stesso nodo padre sono detti fratelli. Un oggetto nodo possiede due proprietà, previousSibling e nextSibling, che restituiscono rispettivamente il nodo fratello precedente e successivo, nell'ordine in cui i nodi appaiono nel documento XML.

Nel documento di esempio i nodi nome, data_nascita e data_morte del medesimo nodo compositore sono tra loro fratelli.

Otteniamo i nodi figli del primo nodo compositore utilizzando la funzione childElements() creata sopra. Poi proviamo a passare dal primo nodo al nodo fratello

var elements = childElements(root);
//elements[0] -> primo nodo compositore
var siblings = childElements(elements[0]);
document.write(siblings[0].nodeName);
document.write('<br />');
document.write(siblings[0].nextSibling.nodeName);

L'output in Internet Explorer è

nome
data_nascita

E tutto torna. Il primo figlio del primo nodo compositore è il nodo nome e il successivo fratello è il nodo data_nascita

Però lo stesso codice in Firefox e gli altri browser dà questo output

nome
#text

Il successivo nodo fratello di nome in questo caso è considerato un nodo Testo, i caratteri a capo e tabulazione che nel documento si trovano tra i tag </nome> e <data_nascita>.

E' chiaro quindi che se utilizziamo queste proprietà direttamente avremo sempre risultati differenti a seconda del browser in cui il nostro codice Javascript viene eseguito.

Possiamo di nuovo creare delle funzioni personalizzate

function nextSiblingElement(node) {
  var sibling = node.nextSibling;
  while(sibling && sibling.nodeType != 1) {
    sibling = sibling.nextSibling;
  }
  return sibling;
}

La funzione omologa, previousSiblingElement(), potete trovarla negli esempi di codice scaricabili dal sito.

Proprietà firstChild, lastChild

Queste due proprietà di un oggetto nodo restituiscono rispettivamente il primo e l'ultimo dei nodi figli. Anche in questo caso, come a questo punto è facile aspettarsi, i caratteri a capo e tabulazione presenti tra i nodi sono scartati automaticamente da Internet Explorer, ma considerati nodi di tipo Testo dagli altri browser.

Negli esempi di codice sono presenti due funzioni firstChildElement() e lastChildElement() che possono essere utilizzate per ovviare a queste discrepanze.

Metodo getElementsByTagName()

I nodi possono essere estratti per nome dal documento tramite il metodo getElementsByTagName()
node.getElementsByTagName("tag")

che restituisce una lista di nodi discendenti di node e aventi tag come valore della proprietà nodeName.

Esempio:

var nodes = root.getElementsByTagName('nome');
for (var i=0; i < nodes.length;  i++)  {
  document.write('Nome: ' + nodes[i].nodeName);
  /*...*/
}

Ritorna tutti i nodi con tag nome presenti nel documento in quanto abbiamo invocato il metodo sul nodo radice (compositori).

Notate che i nodi nome sono figli dei nodi compositore non del nodo radice. Perchè sia incluso nella lista restituita da getElementsByTagName() è sufficiente che un nodo sia discendente del nodo su cui il metodo è invocato.

Attributi dei nodi

Ad un nodo può essere associata una lista di attributi. Ogni attributo è un oggetto che ha un nome (proprietà name) e un valore (proprietà value).

Nel documento XML di esempio, epoca è un attributo di tutti i nodi compositore. La proprietà attributes di un nodo restituisce la lista di attributi di quel nodo. Si può accedere così alle proprietà di ogni attributo.

Esempio:

nodes = root.getElementsByTagName('compositore');
for (i=0; i < nodes.length; i++) {
  attrs = nodes[i].attributes;
  for(x=0; x < attrs.length; x++) {
    document.write(attrs[x].name + ' | ' + attrs[x].value);
    document.write("<br />");
  }
}

Output:

epoca | classicismo
epoca | romanticismo
epoca | romanticismo

Dato un nodo e il nome di un attributo, il metodo getAttribute() restituisce il valore dell'attributo.

Esempio:

nodes = root.getElementsByTagName('compositore');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].getAttribute('epoca'));
  document.write("<br />");
}

Output:

classicismo
romanticismo
romanticismo

Accedere al contenuto dei nodi Testo

Supponiamo di voler estrarre dal documento un elenco delle date di nascita di tutti i compositori. Si potrebbe essere tentati di scrivere

nodes = root.getElementsByTagName('data_nascita');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].nodeValue);
  document.write("<br />");
}

Viene intuitivo (almeno così è successo a me la prima volta che ho fatto qualcosa di questo genere) pensare al testo all'interno dei tag <data_nascita></data_nascita> come valore del nodo corrispondente. Ma non è così, infatti il risultato del codice sopra riportato è

null
null
null

Il codice corretto è

nodes = root.getElementsByTagName('data_nascita');
for (i=0; i < nodes.length; i++) {
  document.write(nodes[i].childNodes[0].nodeValue);
  document.write("<br />");
}

Che dà il risultato che ci aspettiamo

1756-01-27
1797-01-31
1770-12-17

La proprietà nodeValue di un nodo di tipo Elemento è sempre null. Il contenuto di un nodo di tipo Elemento è a sua volta un nodo (di tipo Testo in questo caso) ed è la proprietà nodeValue di questo nodo figlio (ottenuto tramite childNodes) che contiene il testo che ci interessa.

Un'altra cosa che conviene sottolineare è che childNodes restituisce sempre una lista di nodi per cui anche quando i figli siano solo uno si deve usare l'indice (zero).

Attraversare l'albero del documento

Abbiamo visto le funzioni principali per estrarre informazioni da un documento XML con Javascript. Per finire eccovi una funzione in qualche modo riassuntiva che utilizza molte delle funzioni e metodi visti (ma anche qualcuno che non abbiamo esaminato in dettaglio).

La funzione parte dalla radice del documento o da un altro nodo passato come argomento, scorre ricorsivamente tutto l'albero (o il sottoalbero dato dai soli discendenti del nodo ricevuto come argomento) e stampa una serie di informazioni sulle proprietà dei singoli nodi.

function readXML(node) {
  var cn = node.childNodes;
  var ct = node.childNodes.length;
  for (var i=0; i<ct; i++)  {
    document.write('---- Nodo ' + cn[i].nodeName + 
    ' ----<br />');
    var pn = cn[i].parentNode;
    document.write('Nodo Padre: ' + pn.nodeName);
    document.write("<br />");
    document.write('Tipo:' + cn[i].nodeType);
    document.write("<br />");
    document.write('Nome: ' + cn[i].nodeName);
    document.write("<br />");
    document.write('Valore: ' + cn[i].nodeValue);
    document.write("<br />");
    
    if(cn[i].nodeType == 1) {
      var attrs = cn[i].attributes;
      document.write("---- Attributi ----<br />");
      for(x=0; x < attrs.length; x++) {
        document.write(attrs[x].name + ' = ' + attrs[x].value);
        document.write("<br />");
      }
      document.write("<br />");
    }
    var ps = cn[i].previousSibling;
    if(ps) {
      document.write('Fratello Prec.: ' + ps.nodeName);
      document.write("<br />");
    }
    var ns = cn[i].nextSibling;
    if(ns) {
      document.write('Fratello Succ.: ' + ns.nodeName);
      document.write("<br />");
    }
    document.write('N. Figli: ' + cn[i].childNodes.length);
    document.write("<br /><br />");
    if(cn[i].childNodes.length > 0) {
      readXML(cn[i]);
    }
  }
}
Il codice, come tutti gli altri esempi dell'articolo, è anche incluso nel file js-xml.zip che potete scaricare dal sito.

martedì 20 novembre 2007

PHP: Paginazione dei Risultati di una Query

Capita spesso quando si sviluppano applicazioni per il web di dover visualizzare in una tabella grandi quantità di dati provenienti da una query SQL.

In questi casi costringere gli utenti a scorrere una enorme pagina di dati non sarebbe certo una buona idea. Molto meglio adottare una visualizzazione in pagine multiple con link di navigazione che consentano di passare da una pagina all'altra.

Visto che la paginazione dei risultati di una query è un compito che le nostre applicazioni devono svolgere spesso, si tratta del candidato ideale per una classe php. Scrivendo una classe che svolga questa funzione non saremo costretti a riscrivere lo stesso codice tutte le volte.

PDatagrid è una classe che estrae dati da un database MySQL in base ad una determinata query ed impagina i risultati in un formato tabellare (per impostazione predefinita, ma altri tipi di formato sono possibili) con link di navigazione alle singole pagine di dati.

Il pacchetto contiene i seguenti file

  • pdatagrid.class.php - definizione della classe.
  • docs/ - cartella con la documentazione generata con PHP Documentor (in inglese).
  • example1.php, example2.php - semplici file dimostrativi dell'utilizzo della classe.
  • style.css - esempio di foglio di stile per controllare l'aspetto della tabella.
  • dbconfig.php - costanti per la connessione al database usate dal codice di esempio.
  • composers.sql - script sql per ricreare la tabella MySQL utilizzata negli esempi.

Come utilizzare la classe

L'uso della classe è semplice, ecco comunque tutti i passi da seguire per integrarla nelle vostre applicazioni. Il codice che segue è tratto dal file di esempio (example1.php)

1)
require 'pdatagrid.class.php';

Ovviamente il primo passo è includere il file di definizione della classe.

2)
require 'dbconfig.php';
$conn = mysql_connect(DB_HOST, DB_USER, DB_PASSWORD) 
  or die('Connection to database failed: ' . mysql_error());
mysql_select_db(DB_NAME) or die ('select_db failed!');

Si effettua la connessione al database. Il file dbconfig.php è presente nel pacchetto e deve essere modificato con il nome database, nome utente e password necessari per accedere al vostro database.

3)
$grid = new PDatagrid($conn);

Si crea un'istanza della classe PDatagrid e si inizializza con la connessione al database appena creata.

4)
$grid->setSqlCount("Select count(*) from composers");
$grid->setSqlSelect("Select name, 
  date_format(date_birth,'%b %D %Y') db, 
  date_format(date_death,'%b %D %Y') dd from composers");

Si devono impostare due diverse query SQL. La prima per contare il numero di record che si vogliono visualizzare, la seconda per estrarli con SELECT. Da notare che nell'esempio non c'è nessuna condizione WHERE perché vogliamo visualizzare tutti i record della tabella. Se avessimo bisogno di impostare una condizione WHERE la stessa condizione dovrebbe essere presente in entrambe le query o la paginazione non potrebbe funzionare.

5)
$grid->baselink = 'example1.php';
$grid->setMaxNavLinks(4);
$grid->setRowsPerPage(5);
if(isset($_GET['page']))
  $grid->setPage($_GET['page']);

Si impostano nell'ordine:

  • la url base per i link di navigazione
  • il numero massimo dei link di navigazione. Questi sono i link che puntano alle singole pagine. Se le pagine di dati da visualizzare sono molte avere un link ad ogni pagina occuperebbe troppo spazio, in questo caso si imposta il numero massimo di questi link con setMaxNavLinks().
  • il numero di record da visualizzare su una pagina
  • il numero della pagina corrente. Se $_GET['page'] non è impostato allora siamo sulla prima pagina dei risultati, altrimenti la variabile page nella url indica il numero di pagina corrente (vedere nel codice il formato dei link di navigazione)

La parte rimanente del codice è costituite da semplici istruzioni per generare l'html necessario alla visualizzazione della tabella

6)
<table cellspacing="0" cellpadding="0" width="500">
<thead>
<tr>
  <td width="50%">Name</td>
  <td width="25%">Date of Birth</td>
  <td width="25%">Date of Death</td>
</tr>
</thead>

Intestazioni dei campi

7)
<tfoot>
<tr>
  <td colspan="3">
  <span id="navlinks">
    <?php echo $grid->getLinks();?>
  </span>
  </td>
</tr>
</tfoot>

Link di navigazione nel footer

8)
<tbody>
  <?php echo $grid->getRows();?>
</tbody>
</table>

Le righe della tabella nel body

Foglio di stile

L'aspetto della tabella è controllato interamente attraverso fogli di stile. Tra le altre cose agendo sugli opportuni attributi (fare riferimento ai commenti nel file style.css usato dagli esempi) si può impostare un colore di sfondo alternato per le righe in posizione pari e dispari e modificare l'aspetto dei link di navigazione.

Altri esempi

example2.php dimostra come sia possibile visualizzare i record in un formato diverso dalla tipica griglia.

Per fare questo si utilizzano le proprietà rowfmt e fieldfmt che consentono di impostare un formato di visualizzazione di una riga di dati e del singolo campo.

$grid->rowfmt = "<ul%s>%s</ul>\n";
$grid->fieldfmt = "<li>%s</li>";

In questo modo si utilizza un insieme di liste non ordinate per la visualizzazione dei record. Le stringhe di formato usano la stessa sintassi dell'istruzione sprintf.

Il pacchetto (pdatagrid.zip) è scaricabile dal sito. Per provarlo scompattate il tutto in una directory del vostro sito o webserver locale. Per eseguire gli esempi dimostrativi dei diversi tipi di paginazione dei dati che si possono ottenere con la classe, è necessario anche creare un database, modificare dbconfig.php con i dati per l'accesso allo stesso e lanciare lo script composers.sql per creare la tabella di prova.

I commenti al codice e la documentazione completa (che non contiene però molto di più di quanto spiegato in questo articolo) sono in inglese perché ho creato la classe per un sito estero con cui collaboro e non avevo troppa voglia di tradurre il tutto. Chi incontrasse problemi insormontabili di lingua può contattarmi.

Spero la classe vi torni utile, modifiche e futuri sviluppi sono sempre possibili.