P4A framework: utilizzare il widget P4A Google Maps per implementare uno store locator

 

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

P4A esempio di Store LocatorEcco un esempio di utilizzazione del widget P4A Google Maps per creare una mappa di riferimenti (negozi, magazzini, ecc) che siano compresi in un certo raggio di distanza in Km, a partire da un indirizzo prestabilito.
Ho cercato di utilizzare il widget di Alberto Galanti senza effettuare nessuna modifica al codice originale. Ho scelto, quindi di implementare un metodo che fornisce la georeferenza della località che funge da centro, per poi effettuare, attraverso una query, la selezione degli indirizzi compresi nel raggio di distanze prestabilito.
L’esempio utilizzato, per comodità, è lo stesso indicato dalla guida di Google Maps in questo articolo.

I passi principali prima di procedere:

  • Scaricare il widget P4A Google Maps qui
  • Creare un archivio di riferimenti che abbiano un indirizzo nel formato accettato da Google Maps (Es.: “Piazza Venezia, Roma, Italia”)
  • Georeferenziare tutti gli indirizzi (trovare latitudine e longitudine)

Utilizzando MySQL, la struttura dell’archivio suggerita da Google è questa:

1
2
3
4
5
6
7
8
CREATE TABLE `markers` (
  `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(60) NOT NULL,
  `address` VARCHAR(80) NOT NULL,
  `lat` FLOAT(10,6) NOT NULL,
  `lng` FLOAT(10,6) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

Per comodità potete scaricare l’sql completo dei dati di esempio di Google qui

Nel caso non si conoscano già le coordinate dei riferimenti, è possibile utilizzare il metodo suggerito in questo articolo sempre da Google. Questo sistema permette di aggiornare direttamente i dati di un database MySQL con le coordinate fornite da Google Maps per tutti gli indirizzi memorizzati.

La selezione dei record che sono compresi in un reggio di distanza in Km prefissato, avviene tramite una query che utilizza la formula della triangolazione sferica:
cos p = cos a cos b + sen a sen b cos φ,
per maggiori dettagli potete leggere questo post.

Ecco la query in pratica:

1
2
3
4
5
6
7
8
SELECT address, name, lat, lng, 
( 6371 * acos( cos( radians('Center_latitude')) * cos( radians( lat )) 
* cos( radians( lng ) - radians('Center_longitude')) 
+ sin( radians('Center_latitude'))
* sin( radians( lat )))) AS distance
FROM markers
HAVING distance < 'Radius'
ORDER BY distance

dove 'Center_latitude' è la latitudine del centro, 'Center_longitude' è la longitudine del centro e 'Radius' è il raggio in km dal centro.

Ed ecco dunque, il codice della maschera P4A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class GoogleMap extends P4A_Mask
{
  function GoogleMap()
  {
    parent::P4A_Mask();
    $this->setTitle("Google Map Store Locator Test");
    // Frame
    $this->build("p4a_frame", "frame");
    $this->frame->setWidth(800);
    //la chiave per localhost è disponibile nel file p4a_map.php
    $coordArray = $this->getLocationCoordinates("Mountain View, CA", 
                  "Inserire_chiave_Google"); 
    $radius = 12;
    // DB Source
    $this->build("p4a_db_source", "source");
    $query = "SELECT address, name, lat, lng, 
     CONCAT_WS('
', name, address) AS info,
     (6371 * acos(cos(radians('$coordArray[2]')) * cos(radians(lat))
     * cos( radians(lng) - radians('$coordArray[3]')) 
     + sin( radians('$coordArray[2]'))
     * sin( radians(lat)))) AS distance
     FROM markers
     HAVING distance < '$radius' ORDER BY distance"; $this->source->setQuery($query);
    $this->source->setPk("id");
    $this->source->load();
    // map
    $map =& $this->build("p4a_map","mappa");
    $map->setCenter("Mountain View, CA");
    $map->setZoom(10);
    $map->setWidth(750);
    $map->setHeight(500);
    // markers from datasource
    $map->setSourceAddressField("address");
    //$map->setSourceDescriptionField("distance");
    $map->setSourceDescriptionField("info");
    $map->setSource($this->source);
    // Layout
    $this->frame->anchorCenter($map);
    // Display
    $this->display("main", $this->frame);
   }
 /*
  * Return the coordinates of the location.
  * @param $location		valid Google Maps address of a location.
  * @param $googleMapKey		key for accessing Google Maps.	 
  * @return array  element[0] = status, element[2] = lat, element[3] = long
  */
 function getLocationCoordinates($location, $googleMapKey)
 {
  $base_url = "http://maps.google.com/maps/geo?output=csv&key=".$googleMapKey;
  $request_url = $base_url."&q=".urlencode($location);
  $csv = file_get_contents($request_url);
  if ($csv) {
    $csvSplit = split(",", $csv);
  }
  else{
    $csvSplit = array();
  }
    return $csvSplit;
  }
}

Note:

La query crea la sorgente dati per i markers, che vengono evidenziati automaticamente dalla classe p4a_map.
Al posto delle info, potete visualizzare la distanza di ogni marker dal centro (nell’esempio: “Mountain View, CA”) utilizzando l’istruzione: $map->setSourceDescriptionField("distance");.
Il metodo getLocationCoordinates() recupera le coordinate del centro per poter poi comporre la query.
La chiave per l’accesso a Google Map è quella valida per un server tipo “localhost” e la trovate all’interno del file: p4a_map.php che si trova nella directory libraries. Per avere quella per un dominio pubblico è necessario farla generare a Google Maps qui.
Non ho fatto controlli di errore, ad esempio per quanto riguarda la risposta del metodo getLocationCoordinates() sarebbe opportuno controllare che l’array non sia vuoto e anche lo status della risposta di Google Maps, e si dovrebbe anche limitare il risultato della query al numero di markers massimo gestiti dal widget (17 se non ricordo male…).

Download

Il widget con la maschera sopra descritta è scaricabile qui

Conclusioni:

Il risultato è già soddisfacente, anche se un po’ grezzo, ma credo ci siano molti margini di miglioramento per questo tipo di utilizzo di Google Maps in P4A. Spero di avere tempo per affinare ulteriormente sia questo programma che il widget, per renderlo utilizzabile anche per idee diverse! Suggerimenti e consigli sono molto ben accetti!!

Riferimenti ed approfondimenti:

Adobe Flex: programma per il calcolo delle distanze lossodromiche, ortodromiche ed altri parametri di navigazione

Gerardo MercatoreCon la scusa di dover imparare ad utilizzare Adobe Flex 3, ho cercato, invece del solito “Hello World”, di affrontare un esempio concreto e non troppo banale. Avendo già trattato l’argomento delle distanze geodetiche, ma solo sotto l’aspetto di circolo massimo che passa per due punti (ortodromia), ho voluto affrontare il problema anche sotto l’aspetto delle distanze lossodromiche, molto usate nella navigazione.
Chi va per mare, sa bene che cosa è una distanza lossodromica, per noi “terrestri” invece, la sola parola incute mistero. Per capire meglio l’arcano mi sono documentato e ho imparato che un geniale filosofo fiammingo del 1500, tal Gerardus Mercator allievo del matematico e cartografo Gemma Frisius, fu tra i primi a comprendere che una nave che segue sempre la stessa direzione indicata dalla bussola, non compie la distanza minore (circolo massimo) ma una curva denominata appunto lossodromica che appare come un’elica sferica sulla superficie del globo terrestre. Questa curva taglia i meridiani sempre con lo stesso angolo e questa è la differenza sostanziale rispetto all’ortodromia. Per facilitare il calcolo delle rotte. Mercator costruì una carta
(detta appunto di Mercatore) nella quale i meridiani sono rappresentati come linee rette tra loro parallele, dunque le curve lossodromiche sono pure rappresentate come linee rette mentre le ortodromiche vengono rappresentate come curve. Questa caratteristica la rende molto adatta al calcolo delle rotte per distanze brevi (minori di 500-1000 nm).

Fatta questa doverosa premessa, vediamo quali sono i parametri di navigazione più importanti per il calcolo della rotta:

  • Rv – Rotta vera. E’ l’angolo compreso tra la direzione del Nord e la traiettoria seguita dalla barca.
  • Percorso lossodromico in miglia – E’ la distanza percorsa dal punto di partenza e quello di arrivo tagliando i meridiani sempre con lo stesso angolo
  • Rotta iniziale quadrantale – E’ compresa in uno dei 4 quadranti, a partire da nord, oppure da sud ed è preceduta dal cardine N o S e seguita dal cardine E o W. Ha quindi un valore compreso tra 0° e 90°)
  • Percorso ortodromico in miglia – E’ l’arco di circolo massimo compreso tra i punti di partenza e di arrivo
  • Differenza percorso lossodromico-ortodromico

Il programma

screenshot del programma
screenshot del programma

Per la realizzazione del programma, ho utilizzato le formule che sono pubblicate in questo sito, in una una tabella sintetica. E’ anche possibile scaricare un foglio di calcolo Excel opportunamente predisposto all’utilizzo di queste formule. Tutti gli algoritmi che ho utilizzato nella classe che ho costruito, sono fedelmente ricavati da questo foglio di calcolo. Questo mi è stato molto utile perché mi sono potuto concentrare sul linguaggio di programmazione che per me era completamente nuovo. Inoltre ho potuto anche verificare la corrispondenza dei risultati.
Una caratterisitica di Adobe Flex è quella di mettere a disposizione un linguaggio di tag, derivato dall’XML per la rappresentazione
dell’interfaccia grafica che si chiama MXML, e un linguaggio vero e proprio di programmazione che si chiama Action script. Questo linguaggio è fortemente orientato agli oggetti perlomeno nella versione 3, ed è piuttosto rigoroso. Somiglia per alcuni aspetti a javascript e per molti altri a Java. La compilazione del progetto produce un file swf, che necessita per la sua esecuzione di una macchina virtuale (l’Actionscript Virtual Machine) questa è disponibile come plugin dei browser e si chiama Flash player. Con l’avvento di Adobe Air, è ora anche possibile creare applicazioni desktop (breve presentazione di AIR).

Potete usare il programma seguendo questo link (necessario il plugin Flash player). Il codice è disponibile facendo click con il tasto destro sull’applicazione, e selezionando dal menù la voce “view source”. Nella finestra che si apre, oltre all’esposizione del codice con syntax highlighting, è presente anche un link per poter scaricare l’intero progetto in formato compresso zip. Naturalmente, trattandosi della mia prima applicazione Flex, ci saranno sicuramente errori, …spero solo di forma! Per questo consiglio di scaricarvi anche il foglio di calcolo del navigatore Roberto Iori e fare qualche confronto sui risultati. Se trovate evidenti incongruenze, scrivetemi pure! Se invece le differenze sono molto piccole, questo può essere imputato a diversi fattori: gli arrotondamenti dei decimali, il modo in cui il processore numerico calcola i numeri in virgola mobile, ecc.

Conclusioni:

Ho cercato di unire l’utile al dilettevole, e ci sono riuscito! E’ stato dilettevole imparare cose nuove riguardo la navigazione, argomento davvero affascinante. E’ stato oltremodo utile iniziare a conoscere lo strumento di Adobe. La piattaforma è molto potente e soprattutto flessibile. Con l’ausilio di Adobe Flex Builder (che è una sorta di Eclipse modificato ad hoc) creare le interfacce grafiche con il drag’n’drop è molto semplice, inoltre l’ausilio del syntax highlighting e autocomplete fanno davvero comodo. Il builder di Adobe non è troppo costoso, soprattutto se paragonato ad altri prodotti rivali di casa Microsoft, costa 214,80 euro (Adobe Flex Builder 3 Standard). Poi, rinunciando alle comodità del builder, c’è anche a disposizione un SDK completamente gratuito.

Riferimenti ed approfondimenti:

PHP: Usare la libreria cURL per l’upload di file tramite FTP e la gestione sincrona del risultato

Logo librerie cURLLa libreria cURL è stata scritta da Daniel Stenberg
per la connessione attraverso diversi tipi di protocolli fra i quali FTP, http, https, telnet a server remoti. Di particolare interesse è la possibilità di effettuare l’upload via FTP con l’estensione ftp di PHP. Il test che propongo riguarda l’utilizzo di alcune funzioni di questa libreria per l’invio di un file su un server remoto e la gestione sincrona dei dati inviati attraverso il file.
Più semplicemente, invio un file utilizzando il protocollo FTP, attendo che il file sia arrivato senza errori, e poi effettuo il parsing del suo contentuto lanciando uno script sul server passandogli come parametro POST il nome ed il percorso di destinazione del file stesso.

Per prima cosa bisogna accertarsi che il PHP integri le librerie cURL. L’installazione non sembra essere troppo complessa, vi rimando al manuale PHP nella sezione installazione.
I più fortunati, (come me!) che utilizzano WAMP5 non dovranno far altro che abilitare in Impostazioni PHP -> Estensioni PHP -> php_curl.

La sostanza delle funzioni cURL è tutta in questi pochi step:

  • Inizializzare una sessione usando curl_init()
  • Impostare le opzioni per il trasferimento tramite curl_setopt()
  • Eseguire la sessione usando curl_exec()
  • Terminare la sessione con curl_close()

Vediamo il codice dello script testcurlftp.php che effettua l’upload del file:

1
2
3
4
5
6
7
8
<!--?php // ------- Settings --------------------------- $local_path = "c:/wamp/www/test/"; //local path of the file to send $file_name = "test.txt"; //file name $ftp_user = 'user'; // ftp username $ftp_pass = 'password'; // ftp password $ftp_location = "ftp.yoursite.com/httpdocs"; // ftp site destination $http_path = "/files/"; // relative http path //server script location: $server_script_url = "http://yoursite.com/files/testscript.php"; // ------- End Settings ------------------------ $fileToSend = $local_path.$file_name; $ftp_url = "ftp://".$ftp_user.":".$ftp_pass."@" .$ftp_location.$http_path.$file_name; $errorMsg = ''; // ------- Upload file through FTP --------------- if (is_file($fileToSend)){ $ch = curl_init(); $fp = fopen ($fileToSend, "r"); // we upload a TXT file curl_setopt($ch, CURLOPT_URL, $ftp_url); curl_setopt($ch, CURLOPT_UPLOAD, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); // set size of the file, which isn't _mandatory_ but // helps libcurl to do extra error checking on the upload. curl_setopt($ch, CURLOPT_INFILESIZE, filesize($fileToSend)); $res = curl_exec ($ch); $errorMsg = curl_error($ch); $errorNumber = curl_errno($ch); curl_close ($ch); // Run server script with the uploaded file name passed as a // POST parameter if ($errorNumber==0){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $server_script_url); //Return a string as the result of curl_exec() curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, 'fileuploaded='.$http_path.$file_name); $res = curl_exec ($ch); $errorMsg = curl_error($ch); $errorNumber = curl_errno($ch); curl_close ($ch); echo $res; //DEBUG } if ($errorNumber&gt;0){
    print 'cURL error: '.$errorMsg." - n.".$errorNumber; // DEBUG
  }
}
else{
  print 'Cannot find file:'.$fileToSend; //DEBUG
}
?-->

Questo invece è il codice dello script testscript.php che gira sul server remoto:

1
<!--?php $res = "ERRORE"; if (!empty($_POST['fileuploaded'])){ $filename = $_SERVER['DOCUMENT_ROOT'].$_POST['fileuploaded']; if (is_file($filename)){ $handle = fopen ($filename, "r"); $res = fread($handle, filesize($filename)); fclose($handle); } } print $res; ?-->

Se inviamo il file test.txt che contiene:

<p>Prova invio file con librerie <em>cURL</em> PHP</p>

otteniamo:

Prova invio file con librerie cURL PHP

Download:

Potete scaricare gli script qui.

Conclusioni:

Questi due script sono solo un piccolo test che lascia intravedere le enormi potenzialità di queste librerie. Immaginiamo per esempio di dover aggiornare un database remoto mantenendo il controllo degli errori sia sul trasferimento dei dati che sull’esecuzione delle query. Va detto, inoltre che la libreria mette a disposizione due funzioni per la gestione degli errori: curl_error() e curl_errno() attraverso le quali è possibile implementare un sistema avanzato di log della procedura, e infine attraverso la funzione curl_getinfo() è possibile ottenere informazioni sul tempo di trasferimento, la data del documento ricevuto, il content-type dell’oggetto scaricato e numerose altre.
Interessante è anche la possibilità di lanciare lo script dal server locale attraverso una procedura batch, magari schedulata attraverso le operazioni pianificate (per far questo bisogna ricordarsi di caricare il php.ini che integra la libreria cURL attraverso l’uso del parametro -c).
Per maggiori informazioni su questo utilizzo vi rimando al solito manuale PHP sezione: Utilizzo di PHP da linea di comando.

Riferimenti ed approfondimenti:

MySQL: un semplice script batch DOS per il backup

MySQL-logo.jpgUn aspetto importante nella distribuzione di web application per piccole e medie aziende è il backup degli archivi. Nel caso, piuttosto frequente per questo target, che il server MySQL sia locale (all’interno della LAN) è possibile utilizzare l’utility mysqldump.exe distribuita insieme al pacchetto MySQL. Questa piccola applicazione client fornisce un output direttamente in SQL oppure in altri formati standard come ad esempio CSV e XML. Nel caso in cui le tabelle del database siano esclusivamente di tipo MyISAM, si può anche prendere in considerazione l’utilizzo di un’altra utility fornita insieme a MySQL che si chiama mysqlhotcopy che è uno script in PERL decisamente più veloce, ma inadatto a database di tipo innoDB.

Ci sono tre modi di lanciare mysqldump:

mysqldump [options] dbname [tables]
mysqldump [options] --databases db_name1 [db_name2 db_name3]
mysqldump [options] --all-databases

Nel primo caso è possibile specificare quali tabelle di un database processare per il backup, nel secondo è possibile specificare un certo numero di database mentre nell’ultimo caso tutti i database presenti vengono processati per il backup.

I parametri opzionali sono numerosi, quello che mi sembra più utile è --opt che raggruppa insieme una serie di opzioni che sono utilizzate frequentemente:

  • --add-drop-table
  • --add-locks
  • --create-options
  • --disable-keys
  • --extended-insert
  • --lock-tables
  • --quick
  • --set-charset

Nella sezione “User comments” del manuale di MySQL on-line relativa a MySQLDump viene segnalato un interessante script per automatizzare la procedura di backup su macchine dotate di sistema operativo Windows scritta in puro batch script DOS. Questo script è davvero completo e oltre al backup di tutti i database implementa anche la compressione dei file generati e l’invio di una mail all’amministratore del RDBMS

Poiché in molti casi si rende necessario solo il backup di uno o due database e non è necessaria né la compressione né l’invio delle mail, ho semplificato lo script lasciando solo le sue funzioni fondamentali che sono:

  • Creazione dei file sql per ogni db presente nella lista con un prefisso che indica la data e l’ora del backup
  • Creazione di un unico file di log che indica la data e l’ora di inizio e di fine backup e l’elenco dei database processati

Ecco il codice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@echo off
:: ---- SETTINGS ----------------------------------------
:: db user name
set dbuser=root
:: db user password
set dbpass=
:: db list (separate each db name with a blank space)
set dblist=magazzino p4a_products_catalogue
:: backup directory 
set bkupdir=C:\dbBackups\files
:: MySQL folder
set mysqldir=C:\wamp\mysql
:: -----------------------------------------------------
:: Do not edit here
for /f %%i in ('doff.exe yyyymmdd_hhmiss') do set fn=%%i
for /f %%i in ('doff.exe dd-mm-yyyy_hh:mi:ss') do set nicedate=%%i
echo MySQLDump start on %nicedate% &gt;&gt; log.txt
for %%f in (%dblist%) do (
echo backing up %%f ...
"%mysqldir%\bin\mysqldump" --user=%dbuser% --password=%dbpass% --databases %%f --opt 
--quote-names --allow-keywords --complete-insert &gt; "%bkupdir%\%fn%_%%f.sql"
echo Done! 
echo backup of %%f done! &gt;&gt; log.txt
)
for /f %%i in ('doff.exe dd-mm-yyyy_hh:mi:ss') do set nicedate=%%i
echo MySQLDump finished on %nicedate% &gt;&gt; log.txt
echo -- &gt;&gt; log.txt
echo All Done!

L’utilizzo è semplicissimo, è sufficiente impostare nella sezione “settings” i parametri che indicano username e password per l’accesso al server MySQL, la lista dei nomi dei database da processare, il percorso della directory di backup e il percorso della directory di installazione di MySQL.
All’interno dello script viene richiamato un programmino (doff.exe) che serve a restituire la data e l’ora di sistema in un formato utilizzabile per generare il suffisso dei files generati.
Infine un’avvertenza: nel codice che ho riportato qui sopra sono stato obbligato a spezzare la linea che contiene il comando di lancio di mysqldump, chi volesse copiare ed incollare questo codice dovrà eliminare il ritorno carrello che spezza la riga! Per semplificare, potete scaricare direttamente lo script insieme all’albero di directory per il backup e all’utility “doff.exe” qui

Chi utilizza server Linux potrà facilmente convertire questo in uno script bash. Inoltre ho trovato (ma non testato…) uno script per linux che funziona in modo simile: Shell script to backup MySql database

Riferimenti ed approfondimenti


Maurizio ha scritto:

Eccellente, coniuga perfettamente semplicità praticità e sopratutto funzionalità . Voto mio 10.
Grazie 1000

Massimiliano ha scritto:

Grande Script.
Ho una domanda.

Se lancio lo script manualmente dalla Prompt dei comandi tutto funziona correttamemte.
Mentre se lo metto in operazioni pianificate di windows il nome del file di backup pianificato non contiene le info su data ed ora.
Ossia invece di chiamarsi
20090907_120000_bk.sql
si chiama
_bk.sql

Da quanto ne capisco non viene eseguito doff.exe
Qualche consiglio?

L’utente utilizzato per schedulare il bat è quello di administrator

Mario Spada (Autore) ha scritto:

Grazie Massimiliano! Il problema è che, lanciando lo script da una posizione differente dalla directory in cui risiede (come avviene nelle operazioni pianificate), il percorso di doff.exe non è corretto. Per risolvere basta anteporre il path completo a doff.exe. Quindi, se la directory dove risiede lo script + doff.exe è C:\dbBackups\:
riga 15 diventa:
for /f %%i in (‘C:\dbBackups\doff.exe yyyymmdd_hhmiss’) do set fn=%%i
riga 16 diventa:
for /f %%i in (‘C:\dbBackups\doff.exe dd-mm-yyyy_hh:mi:ss’) do set nicedate=%%i
e riga 24 diventa:
for /f %%i in (‘C:\dbBackups\doff.exe dd-mm-yyyy_hh:mi:ss’) do set nicedate=%%i
oppure (meglio) crea una variabile con il path nella sezione “SETTINGS”

P4A framework: gestire maschere master-detail multiple con tab e div a scomparsa

schema databaseVorrei proporre un esempio un po’ particolare, ma non raro, di gestione di maschere nel caso la classica situazione master-detail sia leggemente più complessa.
Immaginiamo di avere la tabella master degli ordini e la tabella detail del dettaglio dell’ordine. Supponiamo però che il nostro utente abbia necessità di impostare nell’ordine un certo modello (si può pensare ad un kit) di prodotto al quale legare un certo numero di componenti e di accessori.
Per riassumere in un database sufficientemente normalizzato, questo tipo di informazione, si può ricorrere allo schema illustrato nella figura a fianco. In questo modo la chiave primaria della tabella dettaglio_ordine, verrà associata ad ogni singolo modello dell’ordine, e per ogni modello sarà possibile associare un certo numero di componenti ed accessori attraverso le rispettive tabelle di relazione.

Come rendere adesso ergonomica la visualizzazione delle maschere? La maschera dettaglio_ordine, infatti dovrebbe contenere anche dettaglio_componenti e dettaglio_accessori.
applicazione di provaLa soluzione che ho scelto, è di sistemare la master ordini in un tab, e tutte le detail in un altro tab dove, attraverso un combo box, sia possibile visualizzare a scelta la maschera dettaglio_componenti oppure la dettaglio_accessori o tutte e due, lasciando solo la gestione dei modelli sempre in evidenza.
Per realizzare questa soluzione, bisogna ancorare tutti i componenti (tabella, fieldset e toolbar) delle maschere per gestire componenti e accessori in due appositi frame e impostare il metodo setVisible() a FALSE o a TRUE rispettivamente per nascondere o mostrare la maschera. Per prima cosa, nel costruttore della classe principale, definiamo le source per le maschere secondarie (per brevità inserisco qui solo la parte di codice relativa ai div a scomparsa, ma tutto il codice è comunque scaricabile in fondo a questo articolo)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//.......
//Build main detail source
$dett_ordine= &amp; $this-&gt;build("dettaglio_ordine", "dett_ordine");
$dett_ordine-&gt;dettaglio_ordine-&gt;addFilter("ordini_idordini = ?", 
                                      $this-&gt;ordini-&gt;fields-&gt;idordini); 
$this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;firstRow();
 
//Build component detail source
$cmpntidtglo= &amp; $this-&gt;build("componenti_dettaglio", "cmpntidtglo");    
$cmpntidtglo-&gt;componenti_dettaglio-&gt;addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;iddettaglio_ordine); 
$cmpntidtglo-&gt;componenti_dettaglio-&gt;firstRow();
 
//Build accessories detail source
$accesrdtglo= &amp; $this-&gt;build("accessori_dettaglio", "accesrdtglo");
$accesrdtglo-&gt;accessori_dettaglio-&gt;addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;iddettaglio_ordine); 
$accesrdtglo-&gt;accessori_dettaglio-&gt;firstRow();
//.......

Poi costruiamo i due frame per ancorarci le maschere componenti e accessori:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//.......
// Frame order detail (main)
$frm=&amp; $this-&gt;build("p4a_frame", "frm");
$frm-&gt;setWidth(800);
// Frame component
$frmcmpntidtglo=&amp; $this-&gt;build("p4a_frame", "frmcmpntidtglo");
$frmcmpntidtglo-&gt;setWidth(700);
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;table_componenti_dettaglio); 
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;tool);
$frmcmpntidtglo-&gt;anchor($cmpntidtglo-&gt;frm);  
// Frame accessories
$frmaccesrdtglo=&amp; $this-&gt;build("p4a_frame", "frmaccesrdtglo");
$frmaccesrdtglo-&gt;setWidth(700);
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;table_accessori_dettaglio); 
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;tool);
$frmaccesrdtglo-&gt;anchor($accesrdtglo-&gt;frm);
//.......

Costruiamo il combo box per la scelta di visualizzazione

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//.......
//Fieldset + combo to manage views
$fsetShowHide=&amp; $this-&gt;build("p4a_fieldset", "fsetShowHide");
$fsetShowHide-&gt;setWidth(700);
$fsetShowHide-&gt;setTitle("Visualizzazione");
$array_source=&amp; $this-&gt;build("P4A_Array_Source", "array_source");
$a = array(); 
$a[] = array('id'=&gt;1, 'value'=&gt;'nascondi tutto'); 
$a[] = array('id'=&gt;2, 'value'=&gt;'dettaglio componenti');
$a[] = array('id'=&gt;3, 'value'=&gt;'dettaglio accessori');
$a[] = array('id'=&gt;4, 'value'=&gt;'mostra tutto');
$array_source-&gt;setPk('id'); 
$array_source-&gt;load($a); 		
$combo_showhide =&amp; $this-&gt;build("p4a_field","combo_showhide");
$combo_showhide-&gt;setType("select");
$combo_showhide-&gt;setSource($array_source);
$combo_showhide-&gt;setLabel("Mostra");
$fsetShowHide-&gt;anchor($combo_showhide);
//.......

Intercettiamo l’evento change del combo box

1
2
3
4
5
6
//.......
// events onChange 
$combo_showhide-&gt;addAction("onChange");
//Intercept showhide combo action
$this-&gt;intercept($combo_showhide,"onChange","ShowHide");
//.......

Ancoriamo tutto nel secondo tab:

1
2
3
4
5
6
7
8
//.......
$tab2-&gt;anchor($fsetShowHide);
$tab2-&gt;anchor($dett_ordine-&gt;frm);  
$tab2-&gt;anchor($frmcmpntidtglo);
$tab2-&gt;anchor($frmaccesrdtglo);
$frmcmpntidtglo-&gt;setVisible(FALSE);    
$frmaccesrdtglo-&gt;setVisible(FALSE);
//.......

Infine definiamo il metodo per la visualizzazione dei frame

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//.......
  function showHide()
  {
    $idDettOrd=$this-&gt;dett_ordine-&gt;dettaglio_ordine-&gt;fields-&gt;
                                iddettaglio_ordine-&gt;getValue();
    // Cannot show accessories or components tables if thera are no records in
    // models table
    if (empty($idDettOrd))
        {
          $res=1; // Hide all
          $comboValue=$this-&gt;combo_showhide-&gt;getNewValue();
          if($comboValue&gt;1)
          {
          $this-&gt;dett_ordine-&gt;message-&gt;setValue("Impossibile visualizzare 
                                      dettagli: nessun modello presente!");
          $this-&gt;combo_showhide-&gt;setValue(1); // Hide all                                           
          }                                           
        }
     // If there are records in models table
        else
        {
          $res=$this-&gt;combo_showhide-&gt;getNewValue();
        }
    switch ($res)
    {
      case 1: // Hide all
          $this-&gt;frmcmpntidtglo-&gt;setVisible(FALSE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(FALSE);      
      break;
      case 2: // Show components
          $this-&gt;frmcmpntidtglo-&gt;setVisible(TRUE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(FALSE);
      break;
      case 3: // Show accessories
          $this-&gt;frmcmpntidtglo-&gt;setVisible(FALSE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(TRUE);
      break;
      case 4: // Show all
          $this-&gt;frmcmpntidtglo-&gt;setVisible(TRUE);
          $this-&gt;frmaccesrdtglo-&gt;setVisible(TRUE);                
      break;
    }
  }
//.......

C’è da notare che questo sistema risulta utile anche perché il metodo addFilter(), nel caso il parametro passato sia NULL, restituisce un insieme di record non filtrati. Nel nostro caso, quando viene registrato un nuovo ordine, ma non è stato ancora inserito nessun modello, l’identificativo (iddettaglio_ordine) nella tabella dettaglio_ordine non esiste ancora, e quindi le tabelle non filtrate mostrerebbero tutti i record, cosa sgradevole e soprattutto pericolosa.

Raccomandazioni:

Questa applicazione è solo un esercizio, un test per provare alcune soluzioni di visualizzazione delle maschere, non è quindi dotata di particolari controlli indispensabili in produzione. Non mi assumo nessuna responsabilità su un suo possibile cattivo funzionamento. Accetto, invece, volentieri eventuali feedback o proposte alternative!

Download:

pacchetto

Conclusioni:

Proprio oggi Fabrizio Balliano ha annunciato il rilascio della prima pre-versione di P4A3.
Questo mi ha indotto a non approfondire eccessivamente le soluzioni prospettate in questo articolo, in quanto la nuova versione è significativamente differente dalla vecchia. Probabilmente con la nuova versione molte soluzioni avranno una via diversa e, da quanto ho capito finora, ancora più semplice.
Infine, si annuncia una cosa molto gradita: una maggiore velocità di tutte le applicazioni sotto il profilo delle prestazioni!

Riferimenti ed approfondimenti:

Calcolare le distanze geodetiche fra comuni d’Italia in php

coordinate.pngIn un precedente articolo avevo pubblicato il codice di una funzione in ANSI C per il calcolo delle distanze sulla superficie terrestre. Poiché il C e il PHP sono due linguaggi dalla sintassi assai simile, ho deciso di fare la traduzione del codice. Inoltre, per dare un senso pratico al lavoro, ho trovato sul web l’archivio completo dei comuni italiani con le relative coordinate geografiche e su questo ho costruito un programmino PHP interattivo che permette di selezionare due comuni ed ottenere la relativa distanza geodetica, ovvero la lunghezza dell’arco massimo che unisce i due punti rappresentati dalle coordinate dei due comuni.
Va sottolineato che, come spiegato in questo ottimo articolo, per distanze molto piccole la formula che utilizzo fornisce un errore piuttosto grosso, quindi i risultati vanno presi come puramente indicativi.

Ecco il codice della funzione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function disgeod($latA, $lonA, $latB, $lonB){
  define ("R","6371");
  /* Converte i gradi in radianti */
  $lat_alfa = M_PI * $latA / 180;
  $lat_beta = M_PI * $latB / 180;
  $lon_alfa = M_PI * $lonA / 180;
  $lon_beta = M_PI * $lonB / 180;
  /* Calcola l'angolo compreso fi */
  $fi = abs($lon_alfa - $lon_beta);
  /* Calcola il terzo lato del triangolo sferico */
  $p = acos(sin($lat_beta) * sin($lat_alfa)
   + cos($lat_beta) * cos($lat_alfa) * cos($fi));
  /* Calcola la distanza sulla superficie terrestre R = ˜6371 Km */
  $d = $p * R;
  return($d);
}

E questa invece è la funzione che converte i gradi sessagesimali in decimali:

1
2
3
4
5
6
7
8
function deg2dec($d=0,$m=0,$s=0,$direction)
{
  $decimal=($d+($m/60)+($s/3600));
  //South latitudes and West longitudes need to return a negative result
  if (($direction=="S") or ($direction=="W"))
          { $decimal=$decimal*(-1);}
  return $decimal;
}

Per evitare di dover caricare le due select con tutti gli 8104 comuni, cosa che avrebbe rallentato il caricamento della pagina in modo improponibile, ho utilizzato uno script AJAX che provvede a popolare le select dinamicamente con i soli nomi dei comuni di una certa provincia preselezionata.

Potete provare il programma su questo link (Il programma è stato testato con Firefox 2 e con Internet Explorer 7)

Download

L’intero pacchetto (circa 1Mb) comprende anche l’archivio in formato sql, txt, xls e csv.

Conclusioni

Lo sviluppo di questo programmino è stato un esercizio interessante, che mi ha permesso di approfondire anche l’utilizzo e il funzionamento degli script AJAX sui quali credo che tornerò in futuro.

Riferimenti ed approfondimenti: