Nella prima parte di questo articolo ho illustrato come creare le tabelle del DB dove registrare i dati provenienti dal tracker GPS, le principali funzionalità dello script e la possibilità di “agganciare” questo socket direttamente al software Open Source: OpenGTS.
Quest’ultima caratteristica, la ritengo particolarmente interessante in quanto permette di evitare di scriversi tutta la parte che riguarda la visualizzazione dei punti inviati dal tracker, sulle mappe.
Settaggi del socket
Chiave
Valori possibili
Descrizione
VERBOSE
true|false
Se impostato a true fornisce un output dettagliato degli errori
MOVING_THRESHOLD
.05
Soglia minima in Km per la registrazione del dato [.05 = 50 metri]
OPENGTS
true|false
Se impostato a true invia query per le tabelle OpenGTS
IP_ADDR
xxx.xxx.xxx.xxx
Indirizzo IP in ascolto [0 = tutti gli indirizzi]
TCP_PORT
0..65535
La porta TCP da utilizzare
DBHOST
localhost
Indirizzo del DB MySQL
DBUSER
dbuser
utente del DB MySQL
DBPASS
dbpassword
password dell’utente del DB MySQL
DBNAME
gpsd
nome del DB MySQL
POLL_TIME
20|30|60|300|600
Tempo di polling del tracker in secondi
SPEED_CONV
1.609344|1.852
Conversione da Miglia (terrestri|marine) a Km
DFLT_MSG
‘tracker’
Messaggio di default del tracker
SOCK_RCV_TIMEOUT
120
Timeout in secondi per il socket in ricezione
Ed ecco il codice PHP del loop del socket (L’intero script è disponibile qui):
Alcuni mesi fa mi è stato regalato questo questo tracker GPS per studiare come registrare su un database MySQL i dati che vengono trasmessi. L’apparecchio si chiama GPS-102 viene prodotto dalla Cobanch ed è disponibile anche in Italia ad un costo inferiore a 100€ su questo sito: www.nonsoloprevenzione.it
Poiché le informazioni della documetazione allegata sono, come spesso accade, piuttosto scarse, mi sono messo alla ricerca nel Web delle specifiche tecniche e del datagramma per la comunicazione dei dati forniti dal tracker (coordinate, velocità, ecc). Fortunatamente ho trovato questo foglio di calcolo, che si riferisce ai modelli TK102, TK103 ma il protocollo risulta identico e fornisce dunque le informazioni necessarie allo sviluppo del software.
Chi utilizza l’RDBMSMySQL in P4A avrà certamente notato che il framework produce in certi casi, delle tabelle il cui nome termina in ‘_seq’. Queste tabelle servono ad avere una compatibilità nella gestione di tutti gli RDBMS supportati (MySQL, PostgreSQL, SQLite, Oracle). PostgreSQL e Oracle, non hanno lo stesso tipo di gestione delle chiavi primarie con l’attributo ‘AUTOINCREMENT‘, rispetto a MySQL e SQLite. La soluzione adottata è di costruire, per ogni tabella con chiave primaria di tipo ‘AUTOINCREMENT’, delle tabelle accessorie che hanno un campo ‘id’ di tipo ‘AUTOINCREMENT’ e di riferirsi a quest’ultime per ottenre il ‘LAST INSERT ID‘. In questo modo viene ricalcato il comportamento delle chiavi primarie di tipo ‘SERIAL’ in PostgreSQL, e anche quelle di Oracle (leggermente diverse).
Prima di tutto una precisazione: questo articolo è riferito esclusivamente agli utenti MySQL. Se si pensa di utilizzare esclusivamente P4A Framework per la gestione dei dati del nostro database, sarebbe bene in fase di progettazione evitare di usare gli AUTOINCREMENT perché, come detto, non necessari. Nel caso in cui il framework venga utilizzato come ‘backoffice’ per la gestione di dati che vengono alimentati da un form di un sito web, oppure si abbia la necessità di utilizzare una struttura dati pre-esistente con tanto di chiavi primarie ‘AUTOINCREMENT’, potremmo prima o poi trovarci di fronte ad un problema di disallineamento dei valori fra la chiave primaria della tabella e l’id’ della tabella ‘_seq’ associata.
Immaginiamo di avere una tabella “test” con questi due campi:
+----+---------+
| id | name |
+----+---------+
con ‘id’ chiave primaria, P4A creerà automaticamente una tabella “test_id_seq” con una campo ‘id’ chiave primaria e ‘AUTOINCREMENT’, ed userà i valori forniti da quest’ultima sia per l’inserimento di nuovi record nella tabella “test“, che per altre operazioni sulla chiave primaria. Dunque utilizzando un programma esterno al framework per fare degli inserimenti e volendo mantenere allineate le tabelle principali e le tabelle “_seq” sarà necessario, per ogni “INSERT” nella tabella principale, fare altrettanto nella “_seq”! Anche utilizzando questo metodo, potrebbe verificarsi prima o poi il problema di un disallineamento, in questo caso ho pensato potesse utile scrivere un metodo della classe “P4A_DB_SOURCE” da utilizzare in extremis, per risistemare le cose.
Per maggiori informazioni sull’utilizzo degli ‘helper’ in P4A suggerisco questo link
Ecco il codice dell’helper, in pratica viene prelevato il valore massimo della chiave primaria ‘AUTOINCREMENT’ della tabella che funge da sorgente dati, quindi viene eliminata la tabella ‘_seq’ associata e successivamente ricostruita con il valore corretto.
Questo codice deve essere inserito in un file con il nome: “p4a_db_source_resync_seq.php” che deve essere posizionato nella directory “libraries” della nostra applicazione P4A.
Ecco infine un po’ di codice di esempio da aggiungere alla nostra ipotetica maschera di manutenzione del db:
class test extends P4A_Base_Mask
{publicfunction __construct(){
parent::__construct();// DB Source$this->build("p4a_db_source","source")->setTable("test")->setPk("id")->load();$this->setSource($this->source);// ....$this->build("p4a_button","btn_resync")->setLabel("resync")->implement("onclick",$this,"resync_seq_table");// ....}publicfunction resync_seq_table(){$res=$this->source->resync_seq();}// ....}
class test extends P4A_Base_Mask
{
public function __construct() {
parent::__construct(); // DB Source
$this->build("p4a_db_source", "source")
->setTable("test")
->setPk("id")
->load();
$this->setSource($this->source); // .... $this->build("p4a_button", "btn_resync")
->setLabel("resync")
->implement("onclick", $this, "resync_seq_table"); // ....
} public function resync_seq_table()
{
$res = $this->source->resync_seq();
} // ....
}
Conclusioni
Questo metodo dovrebbe essere usato con una certa cautela, e solo come procedura di emergenza. Non consiglio l’utilizzo se c’è la possibilità di accesso in scrittura alla tabella coinvolta da parte di altri utenti.
Di certo, in vita i due non si sarebbero mai potuti dare la mano, essendo vissuti in epoche distanti ben tre secoli. Fibonacci, al secolo Leonardo Pisano, visse infatti a cavallo del 1200 ed era di famiglia benestante essendo il padre (Bonaccio, da qui il nome FiBonacci) il segretario della Repubblica di Pisa e responsabile del commercio con l’Africa. Fu proprio in Africa che ebbe modo di studiare le avanzate tecniche matematiche, al tempo in possesso del mondo arabo. Tartaglia, al secolo Niccolò Fontana visse a cavallo del 1500 fra Brescia e Verona. Era di famiglia tutt’altro che ricca, e rimase orfano di padre quando era ancora molto giovane. Ebbe anche la disgrazia di rimanere sfregiato durante il sacco di Brescia del 1512 ad opera dell’esercito francese. A leggere il suo stesso racconto c’è da stupirsi di come sia riuscito a sopravvivere a ferite così gravi. Rimase diversi giorni senza poter parlare e mangiare cibi solidi proprio a causa dei colpi di spada inferti sulla sua testa, e quando guarì non riuscì più a parlare senza balbettare, da questo il suo soprannome Tartaglia con il quale divenne famoso.
Il triangolo di Tartaglia
Tutti noi ricordiamo, dai tempi delle superiori, la piramide di numeri che compongono il famoso triangolo di Tartaglia, e lo ricordiamo soprattutto in relazione allo studio delle potenze di un binomio.
Ogni riga, riporta i coefficienti dell’espansione del binomio con esponente uguale al numero di riga – 1 (iniziando a contare dal vertice della piramide) Così ad esempio, per esponente 3 avremo: (a + b)3 = 1a3 + 3a2b + 3ab2 + 1b3
La successione di Fibonacci
La successione dei numeri di Fibonacci è nota soprattutto per la particolarità che il rapporto tra un numero e quello precedente, man mano che si procede nella successione, tende alla sezione aurea La successione fu trovata da Fibonacci, rispondendo al quesito: “Supponiamo di avere una coppia di conigli, che dopo un mese è in grado di generare una seconda coppia di conigli, quante coppie avremo in un anno?” (Ammesso che ogni coppia generi sempre e solo una coppia al mese e nessuno muoia…). Il procedimento è molto semplice: ogni numero, ad eccezione dei primi due (1, 1), è la somma dei due che lo precedono. Quindi: F(12) = 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
Dove si incontrano Tartaglia e Fibonacci?
Per capire il punto di contatto fra i due, è necessario modificare la forma del triangolo di Tartaglia, disponendo i numeri nelle celle che costituiscono un triangolo rettangolo piuttosto che isoscele, in questo modo:
I numeri che costituiscono le diagonali ascendenti di questa matrice triangolare, sommati fra loro, generano la successione di Fibonacci.
Ecco dunque una classe PHP che calcola i valori del triangolo di Tartaglia e poi estrae dalle diagonali ascendenti i numeri della successione di Fibonacci.
class tartfibo
{var$tartaglia;var$fibonacci;var$max;function tartfibo($max=10){$this->max=$max;$this->tartaglia=array();$this->fibonacci=array();$this->makeTartaglia();$this->makeFibonacci();}function makeTartaglia (){$this->tartaglia[1][1]=1;$this->tartaglia[2][1]=1;$this->tartaglia[2][2]=1;for($row=3;$row<=$this->max;++$row){$this->tartaglia[$row][1]=1;for($col=2;$col<$row;++$col){$this->tartaglia[$row][$col]=$this->tartaglia[$row-1][$col-1]+$this->tartaglia[$row-1][$col];}$this->tartaglia[$row][$row]=1;}}function makeFibonacci (){for($d=1;$d<=$this->max;++$d){$t=0;for($el=1;$el<=($d+1)/2;++$el){$t+=$this->tartaglia[$d+1-$el][$el];}$this->fibonacci[$d]=$t;}}}
class tartfibo
{
var $tartaglia;
var $fibonacci;
var $max;
function tartfibo($max=10)
{
$this->max = $max;
$this->tartaglia = array();
$this->fibonacci = array();
$this->makeTartaglia();
$this->makeFibonacci();
} function makeTartaglia ()
{
$this->tartaglia[1][1] = 1;
$this->tartaglia[2][1] = 1;
$this->tartaglia[2][2] = 1;
for($row=3; $row< =$this->max; ++$row) {
$this->tartaglia[$row][1] = 1;
for ($col=2; $col< $row; ++$col) {
$this->tartaglia[$row][$col] = $this->tartaglia[$row-1][$col-1]
+ $this->tartaglia[$row-1][$col];
}
$this->tartaglia[$row][$row] = 1;
}
} function makeFibonacci ()
{
for ($d=1; $d< =$this->max; ++$d) {
$t = 0;
for($el=1; $el< =($d+1)/2; ++$el) {
$t += $this->tartaglia[$d+1-$el][$el];
}
$this->fibonacci[$d] = $t;
}
}
}
Demo:
In questa pagina è visibile una demo della classe per n=15.
Download
Il download della classe è disponibile qui.
Conclusioni:
Il triangolo di Tartaglia racchiude nei suoi numeri, diversi piccoli segreti e non è solo un metodo per calcolare i coefficienti binomiali. Provate, per esempio a sommare i numeri di ciascuna riga, partendo dall’alto, danno le potenze di 2. Esistono persino dei frattali! Infine, due grandi matematici, Newton e Pascal hanno rielaborato il suo triangolo per lo studio del calcolo combinatorio. Anche i numeri di Fibonacci, oltre alla relazione con la sezione aurea, hanno implicazioni interessanti, sia con la disposizione geometrica di molte strutture naturali (piante soprattutto) che con l’informatica, per esempio: Fibonacci heap
Sono diversi gli ambiti nei quali può essere necessario raggruppare elementi di un certo insieme in base a differenze registrate attraverso misure sul campo. In questi casi per la determinazione dell’entità delle differenze, viene usata la distanza che può essere considerata al di là del suo significato metrico, una misura della diversità. I campi di applicazione possono essere i più disparati: dagli aspetti biologici come per esempio differenze genotipiche o fenotipiche di individui o popolazioni, agli aspetti socio-economici come redditi, produttività o consumo. Non tutte le variabili, però, possono essere trattate allo stesso modo. Solo le variabili che vengono definite “ad intervallo”, possono essere utilizzate per calcolare distanze di tipo metrico come le distanze euclidee, city-block (o Manhattan) e di Minkowski. Parliamo quindi di variabili quantitative. Negli altri casi, a seconda del tipo di variabile utilizzato (dicotomiche, categoriali, ecc) le differenze possono essere comunque calcolate mediante l’utilizzo di distanze non metriche o ultra-metriche. A questo punto è bene definire cosa sono le distanze metriche. Sono quelle che rispettano i seguenti enunciati:
la distanza tra due punti che non coincidono deve essere positiva;
la distanza tra due punti che coincidono è pari a 0;
la distanza tra due punti deve essere simmetrica (d AB = d BA),
e non contravvengono la proprietà della disuguaglianza triangolare, cioè dati tre punti A, B e C deve essere verificata la condizione che: d AB < d AC + d BC riassumendo, si può dire che sono quelle che seguono la metrica pitagorica.
Fatta questa doverosa premessa, passiamo ad analizzare le formule delle distanze metriche utilizzate nella classe PHP.
Distanze Euclidee
E’ senza dubbio il tipo di distanza più diffuso. La formula per il calcolo deriva dalla formula della distanza fra 2 punti in un piano cartesiano e può essere espressa come:
in cui xik e xjk sono le misure in posizione i e j rispettivamente della k-esima di n variabili.
Distanze city-block (Manhattan)
Viene anche detta distanza assoluta o della metrica del taxi. La sua formula è:
La particolarità di questa formula sta nel fatto che, considerando la distanza euclidea fra 2 punti A e C di un triangolo ABC, e che questa distanza può essere espressa come la radice quadrata della somma dei quadrati costruiti sui cateti del triangolo ABC, la distanza di city-block è la somma dei cateti di questo triangolo. Il concetto risulta forse più chiaro osservando la figura qui sotto.
Non a caso, il nome Manhattan deriva proprio dalla particolare disposizione delle strade del famoso quartiere di New York, che si incrociano ad angolo retto, formando una sorta di scacchiera. In effetti questo tipo di distanza, ben si presta a rappresentare il percorso reale fra due punti che si trovano in prossimità degli angoli opposti di un palazzo (…o grattacielo!)
Distanze di Minkowski
Rappresenta la forma più generalizzata di distanza metrica. La sua formula è:
Si dice di ordine p, e in particolare quando p=1, coincide con la distanza city-block, e quando p=2, coincide con la distanza euclidea, come si evince dalla formula. Per p -> infinito coincide con la distanza di Lagrange o di Chebychev.
La classe PHP che ho preparato permette di calcolare, scegliendo una delle distanze illustrate sopra, una matrice triangolare (nella quale la diagonale assume valori = 0) di confronto fra tutti i campioni passati in input. La matrice dati da passare come parametro al costruttore deve avere questo formato:
class DistanceMatrix
{var$dataArray=null;var$dataSize=0;var$dataVectSize=0;function DistanceMatrix($data){$this->dataArray=array();$this->dataArray=$data;$this->checkData();}function checkData(){$this->dataSize=count($this->dataArray);if($this->dataSize<2){die("Cannot proceed: only one sample, or missing data");}reset($this->dataArray);$this->dataVectSize=count(current($this->dataArray));foreach($this->dataArrayas$key=>$vectData){$vectSize=count($vectData);if($vectSize!=$this->dataVectSize){die("Cannot proceed: different size vectors!");}}}function euclidean ($vect1,$vect2){$qdist=0.0;for($i=0;$i<$this->dataVectSize;$i++){$qdist+=pow($vect1[$i]-$vect2[$i],2);}returnsqrt($qdist);}function manhattan ($vect1,$vect2){$dist=0.0;for($i=0;$i<$this->dataVectSize;$i++){$dist+=abs($vect1[$i]-$vect2[$i]);}return$dist;}function minkowski($vect1,$vect2,$exp){$edist=0.0;for($i=0;$i<$this->dataVectSize;$i++){$edist+=pow(abs($vect1[$i]-$vect2[$i]),$exp);}returnpow($edist,1/$exp);}function printData($distType,$exp=1){if(!method_exists($this,$distType)){die("Sorry, can't find $distType method!");}echo("<h1>Matrix of $distType distances </h1>\n");echo("<table class='datatable' summary='Results'>\n");echo("<thead><tr>");echo("<th> </th>");foreach($this->dataArrayas$key=>$data){echo("<th>".$key."</th>");}echo("</tr></thead><tbody>\n");foreach($this->dataArrayas$keyA=>$dataA){echo("<tr>");echo("<td><strong>".$keyA."</strong></td>");foreach($this->dataArrayas$keyB=>$dataB){if($distType=="minkowski"){printf("<td>%.4f</td>",$this->minkowski($dataA,$dataB,$exp));}else{printf("<td>%.4f</td>",$this->$distType($dataA,$dataB));}}echo("</tr>\n");}echo("</tbody></table>\n");}}
class DistanceMatrix
{
var $dataArray = null;
var $dataSize = 0;
var $dataVectSize = 0; function DistanceMatrix($data)
{
$this->dataArray = array();
$this->dataArray = $data;
$this->checkData();
} function checkData()
{
$this->dataSize = count($this->dataArray);
if($this->dataSize < 2){
die("Cannot proceed: only one sample, or missing data");
}
reset ($this->dataArray);
$this->dataVectSize = count(current($this->dataArray));
foreach ($this->dataArray as $key => $vectData){
$vectSize = count($vectData);
if ($vectSize != $this->dataVectSize){
die("Cannot proceed: different size vectors!");
}
}
} function euclidean ($vect1,$vect2)
{
$qdist = 0.0;
for($i=0;$i< $this->dataVectSize;$i++){
$qdist += pow($vect1[$i]-$vect2[$i],2);
}
return sqrt($qdist);
} function manhattan ($vect1,$vect2)
{
$dist = 0.0;
for($i=0;$i< $this->dataVectSize;$i++){
$dist += abs($vect1[$i]-$vect2[$i]);
}
return $dist;
} function minkowski($vect1,$vect2,$exp) {
$edist = 0.0;
for($i=0; $i< $this->dataVectSize; $i++){
$edist += pow(abs($vect1[$i]-$vect2[$i]), $exp);
}
return pow($edist, 1/$exp);
} function printData($distType,$exp=1)
{
if (!method_exists($this,$distType)) {
die ("Sorry, can't find $distType method!");
}
echo ("<h1>Matrix of $distType distances </h1>\n");
echo ("<table class='datatable' summary='Results'>\n");
echo ("<thead><tr>");
echo ("<th> </th>");
foreach ($this->dataArray as $key=>$data){
echo ("<th>".$key."</th>");
}
echo ("</tr></thead><tbody>\n");
foreach ($this->dataArray as $keyA=>$dataA){
echo ("<tr>");
echo ("<td><strong>".$keyA."</strong></td>");
foreach ($this->dataArray as $keyB=>$dataB){
if ($distType=="minkowski"){
printf("<td>%.4f</td>",$this->minkowski($dataA,$dataB,$exp));
}
else{
printf("<td>%.4f</td>",$this->$distType($dataA,$dataB));
}
}
echo ("</tr>\n");
}
echo ("</tbody></table>\n");
}
}
Demo:
In questa pagina è visibile una demo della classe con dei campioni ipotetici e delle variabili di test.
Download
Il download della classe completa del file di esempio è disponibile qui.
Conclusioni:
La naturale conclusione della produzione di una matrice di distanze è l’elaborazione di una cluster analisys. Questo metodo permette di leggere visivamente i raggruppamenti dovuti alle similarità o diseguaglianze indicate dalle distanze calcolate. Tali raggruppamenti potranno essere espressi in forma gerarchica. Un software distribuito con licenza GNU che permette queste elaborazioni, e che suggerisco è Cluster 3.0, disponibile per Linux, Windows e MacOSX.
Sulla personalizzazione dell’aspetto grafico delle maschere di P4A, esiste già questo interessante screencast di Fabrizio Balliano. Trovo interessante poter cambiare la grafica perché l’aspetto di default, sebbene gradevole e riposante, in alcuni casi, per esempio sugli schermi dei netbook appare un po’ troppo poco contrastato. Il tema alternativo proposto nello screencast è “Human” per Gnome, davvero molto bello! Il set di impostazioni per i colori e il set di icone vengono generati a partire da script PHP. Entrambi gli script sono disponibili in P4A Wiki nella sezione: “Theme customizations”, il requisito è di farli girare su una macchina Linux, che sia dotata del necessario software di conversione da SVG a PNG. Ho pensato di scrivere questo post, anche se in gran parte è una ripetizione dello screencast di Fabrizio, perché qualcuno si potrebbe trovare in difficoltà scoprendo al primo approccio che lo script che genera il set di icone, per funzionare correttamente, necessita di una particolare libreria.
Dunque, la situazione migliore è che possediamo una distro “Ubuntu“, quindi con installato il Desktop Gnome. Per prima cosa, ci dobbiamo accertare che sia installato il programma rsvg che fa parte della libreria librsvg. Se non è presente (in genere non lo è…), l’installazione è davvero molto semplice utilizzando apt-get:
sudo apt-get install librsvg2-2
invece, se volete perdere tempo e avete già installate tutte le librerie per il supporto alle manipolazioni dei formati SVG, potete installare il pacchetto manualmente. Queste librerie sono comunque necessarie: libxml2 e libxml2-dev
Adesso copiate le icone generate che saranno raggruppate nelle cartelle con il nome 16 e 32 nella directory human che si deve trovare all’interno di p4a/icons e aggiungete il seguente codice all’interno del file index.php della vostra applicazione P4A, prima della riga di codice: require_once dirname(__FILE__) . '/../../p4a.php'; e con le icone abbiamo finito.
define('P4A_ICONS_NAME','human');
define('P4A_ICONS_NAME', 'human');
In pratica lo script recupera le icone che servono a p4a e le copia, creando la struttura di directory necessaria, nella cartella /directory/che/volete. Se non sono disponibili nel formato richiesto, le genera dai modelli scalabili in SVG, oppure le cerca fra quelle del tema Gnome. Naturalmente, è sempre possibile aggiustare alcuni set a mano. Per esempio, ho notato che nella generazione del tema “Human”, l’icona “folder open”, risulta di colore grigio ed è piuttosto differente da “folder”, così l’ho sostituita con quella generata per il tema “Tangerine”.
Per quanto riguarda lo schema dei colori, dovete procurarvi lo script PHP: gtkrc2p4a.php per importare gli schemi Gnome gtkrc. Per generare il set di colori che armonizza le icone “Human”, digitate da linea di comando:
L’esecuzione genera il seguente set di impostazioni da copiare e incollare dentro index.php (sempre prima di: require_once dirname(__FILE__) . '/../../p4a.php';):
Sembra fatta, ma c’è ancora qualcosa di imperfetto. Se nella vostra applicazione è presente un “tab-panel” i bordi rimangono colorati dell’azzurro di default, e lo sfondo del bottoncino per il passaggio da un pannello all’altro, rimane azzurro chiaro. Così per essere pignoli rimane da modificare un poco il CSS in questo modo:
Create un file provacss.php con le seguenti istruzioni per il CSS (io l’ho messo nella directory libraries della mia applicazione):
E il gioco è fatto! In pratica, è possibile aggiungere un CSS dinamico che carica, mediante querystring, le costanti determinate dal tema adottato.
Ecco uno screeshot della mia applicazione per la gestione delle fatture “vestita” con Human-gnome:
Download
Per chi non avesse a disposizione un ambiente Linux (ma cosa aspetta a farlo?) metto a disposizione il set di icone Human che ho generato qui
Conclusioni
Una volta compreso il meccanismo, personalizzare p4a è piuttosto semplice. Mi sono divertito anche a creare un tema “Vista like”, tanto per non spiazzare troppo i clienti “allineati” a M$…
Riferimenti ed approfondimenti:
Screencast di Fabrizio Balliano: “Theme customizations in RC5”
P4A Wiki
Fabrizio Balliano ha scritto:
ottimo articolo come sempre, vedo però di risolvere il problema del tab pane perché va fixato :) 23.02.09 09:41 Fabrizio Balliano ha scritto:
Questo sito Web utilizza i cookie in modo che possiamo fornirti la migliore esperienza utente possibile. Le informazioni sui cookie sono memorizzate nel tuo browser ed eseguono funzioni come riconoscerti quando ritorni sul nostro sito Web e aiutare il nostro team a capire quali sezioni del sito Web trovi più interessanti e utili.
This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.
Cookie strettamente necessari
I cookie necessari contribuiscono a rendere fruibile il sito web abilitandone funzionalità di base quali la navigazione sulle pagine e l'accesso alle aree protette del sito. Il sito web non è in grado di funzionare correttamente senza questi cookie.
Se disabiliti questo cookie, non saremo in grado di salvare le tue preferenze. Ciò significa che ogni volta che visiti questo sito web dovrai abilitare o disabilitare nuovamente i cookie.
Cookie di terze parti
Questo sito web utilizza Google Analytics per raccogliere informazioni anonime come il numero di visitatori del sito e le pagine più popolari.
Mantenere questo cookie abilitato ci aiuta a migliorare il nostro sito web.
This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.
Keeping this cookie enabled helps us to improve our website.
Attiva i cookie strettamente necessari così da poter salvare le tue preferenze!