P4A 3 Framework: La nuova release 3.2.0, realizzare una maschera con una riga di codice e tabella editabile

screenshot della grid di P4A 3.2.0
screenshot della grid di P4A 3.2.0
La nuova versione del framework PHP P4A si presenta nel “versioning” con un salto di minor di 2 punti (da 3.0.3 a 3.2.0). Questo indica che le novità sono molte e significative. Forse Fabrizio e Andrea erano in vena di regali natalizi…! Strano, però che non ci sia stato ancora un annuncio ufficiale, io l’ho scoperto quasi per caso. Avevo letto in questo post di Andrea che il lancio era imminente, ma non mi sarei aspettato di trovare nell’area download di sourceforge il pacchetto P4A 3.2.0 già disponibile. Siccome le nuove caratteristiche mi stuzzicavano, l’ho subito provato.

Fra le novità più rilevanti c’è il cambio di tipo di licenza che passa dalla AGPL3 alla LGPL3. Non sono un esperto in materia, e non so dirvi molto. Da quanto ho capito si tratta di una differenza formale che regolamenta l’utilizzo e interazioni fra Librerie, Applicazioni e Lavori combinati.

Passando alle note tecniche, a parte una serie di patch e fix che risolvono alcuni bug, le novità più interessanti sono due. Una è quella del nuovo widget P4A_grid. Si tratta in pratica di una tabella editabile, molto utile per modificare singoli campi senza dover per forza utilizzare il fieldset e la toolbar.
La P4A_grid eredita dalla P4A_table tutti i metodi e le proprietà, in aggiunta presenta i seguenti:

  • void autoSave ()
  • void getRows ($num_page, $rows)
  • void preChange ([$params = null])
  • void saveData ($obj, $params)

Non ho avuto tempo di provare tutto, ma ecco un piccolo test con una tabella di prova per un’ipotetica gestione fatture:

1
2
3
4
5
6
7
  // grid
  $this->build("p4a_grid", "grid")
  ->setWidth(600)
  ->setSource($this->source)
  ->setVisibleCols(array("numero", "descrizione", "cliente"))
  ->showNavigationBar()
  ->autosave();

Il risultato è quello della immagine in alto, chiaramente il campo “lookup” non è editabile, ma il risultato è comunque fantastico. Se poi ci fosse la possibilità di riconoscere automaticamente i campi boolean e renderizzarli con una ckeckbox, e le “lookup” in select sarebbe ancora meglio! (dal post di Andrea sembra che la cosa sia già in cantiere…) Se avesse a disposizione anche un metodo per aggiungere record e cancellarli, sarebbe davvero perfetta, benché con un poco di codice e l’utilizzo dei metodi newRow() e deleteRow() questo dovrebbe essere già possibile.

Dulcis in fundo una piccola chicca, che trovo fantastica!
La possibilità di generare un’intera maschera con tutte le funzionalità per interagire con una tabella, scrivendo due sole righe di codice:

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Base_Mask
{
	public function __construct()
	{
		parent::__construct();
		$this->constructSimpleEdit("clienti");
	}
}

Ma forse due righe sono troppe… ecco come fare scrivendone una sola!

1
2
3
4
5
6
7
8
class test_clienti extends P4A_Simple_Edit_Mask
{
 
	public function __construct()
	{
	 parent::__construct("clienti");
	}	
}

Questo il risultato in uno screenshot:

screenshot della P4A_Simple_Edit_Mask
screenshot della P4A_Simple_Edit_Mask

Nel primo caso utilizzo un nuovo helper (oggetto comodissimo particolarmente per il riutilizzo del codice) che si chiama: P4A_Mask_constructSimpleEdit() e nel secondo una classe che deriva da P4A_Base_Mask

Ci sono numerose altre novità come le migliorie nel comportamento dei CSS, la maggiore aderenza agli standard XHTML ed altri ancora che sono elencati nel file CHANGELOG.

Conclusioni

In attesa della presentazione ufficiale, mi sono permesso di elencare i due aspetti che più mi sono piaciuti! Lunga vita a P4A!

Riferimenti ed approfondimenti:

P4A3 framework: un widget orologio che utilizza jClock e jQuery

Widget p4a clockHo approfittato della necessità di presentare un piccolo orologio in una maschera di un progetto al quale sto lavorando, per scrivere l’integrazione del plugin javascript jClock sviluppato con jQuery nel framework P4A 3.
Nulla di spettacolare, siamo d’accordo, ma la scelta è caduta su jClock perché P4A supporta nativamente jQuery, e mi è sembrata la cosa più logica seguire quella direzione.

Che cosa fa?

Mostra un orologio digitale con l’ora del client, configurabile nell’aspetto e nel comportamento attraverso opportuni metodi.

Elenco dei metodi:
NomeTipoDefaultNote
setTimeNotation()string24hValori possibili 12h o 24h. Determina il modo di visualizzazione dell’orario
setAm_pm()stringfalseSe impostato a “true”, mostra A.M./P.M. (Solo se timeNotation() = “12h”).
setUtc()stringfalseSe impostato a “true”, mostra il tempo utilizzando UTC.
setUtc_offset()integer0Valori possibili: da -12 a +12, imposta l’offset da UTC.
setFontFamily()stringNULLSe specificato, imposta il set di caratteri per lo stile CSS, altrimenti eredita.
setFontSize()stringNULLSe specificato, imposta la dimensione in pixel dei caratteri per il testo (p.e.: “14px“), altrimenti eredita.
setForeground()stringNULLSe specificato, imposta il colore del testo (p.e.: “white“), altrimenti eredita.
setBackground()stringNULLSe specificato, imposta lo sfondo del testo (p.e.: “#4b718a“), altrimenti eredita.

Il codice per inserire l’orologio in una maschera di esempio:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test_clock extends p4a_base_mask
{
 public function __construct()
 {
   parent::__construct();
   $this->setTitle("test Clock");
   $clock = $this->build("P4A_jClock","clock");
   $clock->setStyleProperty("width","150px");
   $clock->setStyleProperty("float","none");
   $clock->setStyleProperty("margin","auto");
   $clock->setTimeNotation("12h");
   $clock->setAm_pm(true);
   $clock->setFontSize("14px");
   $clock->setForeground("white");
   $clock->setBackground("#4b718a");
   $this->frame->anchorCenter($clock);
  }
}

Il pacchetto completo della classe, della libreria javascript e della maschera di esempio è scaricabile qui

Note:

Al momento, il sistema di scrittura del codice detto “chainability” non funziona solo con i metodi che ho dichiarato nella classe P4A_jClock. Mi riprometto di individuare e correggere l’errore! Sarà molto gradito il feedback di tutti coloro che volessero utilizzare questo widget, per individuare e correggere eventuali altri errori e per apportare nuovi miglioramenti.

Conclusioni:

E’ interessante soprattutto capire come scrivere widget per P4A. Infatti, sebbene P4A framework, particolarmente nella nuova versione, offra già parecchie feature, capita comunque di aver necessità di qualcosa di particolare per le proprie applicazioni!
In futuro potrebbe essere interessante creare un widget per un orologio analogico, ne ho visti di molto belli qui: CoolClock – The Javascript Analog Clock. e già ci sto facendo un pensierino…

Riferimenti ed approfondimenti:


Fabrizio Balliano ha scritto:

Ciao Mario, per “abilitare” la chainability devi semplicemente aggiungere un “return $this” alla fine di ogni metodo della classe, tutto qui :-))) ottimo widget comunque soprattutto per l’utilizzo “non mascherato da p4a” di jquery!
02.07.08 09:00
Mario Spada ha scritto:

Grazie Fabrizio! Codice corretto, adesso funziona anche la chainability per i metodi dichiarati nel widget.
07.07.08 18:34

P4A3 framework: un helper per pulire la directory tmp in ambiente Windows

P4A 3 Logo

Ho scritto questo piccolo “helper” per ovviare ad un piccolo inconveniente che avviene con P4A3, esclusivamente in ambiente Windows. Succede che, se viene utilizzata la funzione: P4A_Output_File(), ad esempio per generare un file pdf, il file temporaneo non viene più cancellato. Ho potuto verificare che in ambiente Linux questo non succede, e tutti i file generati vengono eliminati una volta passati al browser.

Non sono riuscito a capire esattamente il motivo ma credo che il problema risieda ancora una volta nel differente trattamento da parte di Windows dei separatori nei percorsi assoluti dei path (Windows utilizza i \ invece dei /).
Il problema, anche se può risultare fastidioso alla lunga, per l’accumularsi di un eccessivo numero di files nella directory /uploads/tmp, non è di certo grave, e soprattutto, è molto semplice da risolvere. E’ sufficiente infatti lanciare un metodo che ripulisca” la cartella dai file temporanei più vecchi di n ore (o giorni, o minuti).

Questo è il codice della funzione, che per comodità può essere gestito come “helper”, ovvero un metodo personalizzato prontamente disponibile da qualsiasi maschera della nostra applicazione:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function p4a_mask_clean_old_tmp_files($obj, $olderThan)
{
 $fileDeleted = 0;
 $expire_time = empty($olderThan[0]) || !is_numeric($olderThan[0])? 24: $olderThan[0];
 $fileTypes = array("_p4a_*","cache_*");
 if (P4A_OS == 'windows') {
  define('TEMPDIR','\tmp\\');
  $dirToClean =  P4A_UPLOADS_DIR.TEMPDIR;
  foreach ($fileTypes as $fileType){
   foreach (glob($dirToClean . $fileType) as $filename) {
   $fileCreationTime = filectime($filename);
   $fileAge = time() - $fileCreationTime;
    if ($fileAge > ($expire_time * 3600)){
     if (unlink($filename)){
      $fileDeleted++;
     }
    }
   }
  }
 }
 return $fileDeleted;
}

Il file si deve trovare nella directory /libraries all’interno della directory principale della nostra applicazione, e si deve chiamare: p4a_mask_clean_old_tmp_files.php. Il default è impostato su 24 ore.
Per cambiare la scala del tempo trascorso da ore a minuti è necessario cambiare questa riga:
if ($fileAge > ($expire_time * 3600)){
in
if ($fileAge > ($expire_time * 60)){

Il metodo viene richiamato in questo modo:

$res = clean_old_tmp_files(1); // cancella i files più vecchi di un'ora

La funzione restituisce il numero di files cancellati oppure 0

Conclusioni:

Ho già segnalato questa piccola anomalia a Fabrizio Balliano in questo commento, e credo che ben presto, magari con la release ufficiale di P4A3 annunciata per il 24/06/2008, il problema verrà risolto. Nel frattempo, gli utenti Windows, possono usare questo semplice “helper”.

Riferimenti ed approfondimenti:


Fabrizio Balliano ha scritto:

hum, io aveto testato e vedendo che funzionava correttamente non mi sono piu’ interessato alla questione, ma non sapevo che il problema fosse su windows… ora per martedi’ sara’ complicato risolverlo, vedo che posso fare.
22.06.08 11:42
Fabrizio Balliano ha scritto:

bug risolto :-)
22.06.08 13:04
Mario Spada ha scritto:

Grande Fabrizio, e a tempo di record!!

P4A3 framework: stampa di pdf con le librerie Zend_Pdf

logo pdfNelle precedenti versioni del framework P4A non c’era nessuna libreria interna per la generazione dei documenti pdf.
Era quindi necessario adottare librerie esterne quali ezPdf o FPDF.
La versione 3, invece, incorpora la libreria Zend_Pdf che è integrata nello Zend framework.
A dire il vero, mi ero ben abituato ad ezPdf, e nulla vieta di utilizzarlo ancora, ma mi è sembrato doveroso provare anche le funzionalità delle classi Zend, che oltretutto hanno l’innegabile vantaggio di essere comprese nel framework. Da una prima analisi sommaria, mi sembra che hanno un discreto numero di funzioni utili per il disegno, ma non altrettanto per la manipolazione e gestione del testo. Anche per la formattazione delle tabelle, non mi sembra che ci sia nulla di simile a quanto offre ezPdf.

Passiamo all’esempio pratico: per la prova ho utilizzato la solita applicazione di esempio “Products catalogue”, implementando un pulsante che stampa la scheda del prodotto selezionato. Qundi nel costruttore di products.php ho inserito il seguente codice per creare il bottone e associargli l’evento per la stampa della scheda.

...
// Pdf print button ---------------------------
$this->build("p4a_button", "printProduct")
	->setLabel("Print pdf")
	->implement("onclick", $this, "printDoc");
$this->build("p4a_fieldset", "fs_pdf")
	->setLabel("Print product card")
	->anchor($this->printProduct);
// --------------------------------------------
...

Vediamo ora il codice del metodo per la stampa vera e propria del pdf:

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
public function printDoc()
{
  require_once(P4A_SERVER_DIR."/p4a/".P4A_LIBRARIES_PATH."/Zend/Pdf.php");
 
  $imageName = $this->fields->picture->getNewValue(0);
  if (!empty($imageName)){
    $imagePath = P4A_SERVER_DIR . P4A_UPLOADS_PATH .
                  "/" . $imageName;
    $image = Zend_Pdf_Image::imageWithPath($imagePath);
  }
  $brand = $this->fields->brand->getNewValue();
  $model = $this->fields->model->getNewValue();
  $price = $this->fields->price->getNewValue();
 
  $description = $this->fields->description->getNewValue();
  $description = strip_tags($description);
  $wrapDescription = wordwrap($description, 42, "§");    
  $arrDescription = split("§",$wrapDescription);
 
  $doc = new Zend_Pdf; 
  $page1 = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4); 
  $page1->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_COURIER),14); 
  $doc->pages[] = $page1; 
  if (!empty($imageName)){
    $page1->drawImage($image, 50, 720, 150, 750);
  }
  $page1->drawText("Marca: ".$brand,50,700,'UTF-8'); 
  $page1->drawText("Modello: ".$model,50,680,'UTF-8');
  $page1->drawText("Prezzo: ".$price,50,660,'UTF-8');  
  $page1->drawText("Descrizione: ",50,640,'UTF-8');
  $y = 640;
  foreach ($arrDescription as $segment){
    $page1->drawText($segment,160,$y,'UTF-8');
    $y -= 20;
  }
  $pdfData = $doc->render();
  P4A_Output_File($pdfData,"product.pdf"); 
}

Il campo “description” potrebbe essere anche piuttosto lungo, e poiché non ho trovato nessuna funzione o parametri per fissare un box di stampa entro il quale il testo possa essere formattato, ho dovuto preparare la stringa di testo dividendola in elementi di un array di stringhe di lunghezza prefissata. Per farlo senza spezzare le parole, ho usato la funzione php wordwrap() impostando come interruzione di testo un carattere molto poco usato: “§“. Quindi ho usato questo carattere come separatore dei segmenti di testo. Non è proprio una soluzione robustissima, sarebbe opportuno scrivere un metodo apposito più preciso, nell’attesa che le classi Zend_Pdf vengano arrichite come lascierebbe intendere questo post.

Ecco l’elenco dei formati pagina disponibili:

  • Zend_Pdf_Page::SIZE_A4
  • Zend_Pdf_Page::SIZE_A4_LANDSCAPE
  • Zend_Pdf_Page::SIZE_LETTER
  • Zend_Pdf_Page::SIZE_LETTER_LANDSCAPE

E’ anche possibile specificare un formato personalizzato passando direttamente i valori ($x, $y) in punti di 1/72 di pollice.

Questi invece sono alcuni dei font disponibili:

  • Zend_Pdf_Font::FONT_COURIER
  • Zend_Pdf_Font::FONT_COURIER_BOLD
  • Zend_Pdf_Font::FONT_COURIER_ITALIC
  • Zend_Pdf_Font::FONT_COURIER_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_TIMES
  • Zend_Pdf_Font::FONT_TIMES_BOLD
  • Zend_Pdf_Font::FONT_TIMES_ITALIC
  • Zend_Pdf_Font::FONT_TIMES_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD
  • Zend_Pdf_Font::FONT_HELVETICA_ITALIC
  • Zend_Pdf_Font::FONT_HELVETICA_BOLD_ITALIC
  • Zend_Pdf_Font::FONT_SYMBOL
  • Zend_Pdf_Font::FONT_ZAPFDINGBATS

Infine, un esempio del documento pdf generato.

Per maggiori dettagli vi rimando ai link nel paragrafo: “Riferimenti ed approfondimenti”

Note:

E’ necessario scaricare l’ultima versione (2.99.9) preview di P4A3 che però non è ancora troppo stabile e soffre di qualche imperfezione. Quelle già note sono state corrette, quindi è necessario attingere al SVN per il corretto funzionamento del framework. In particolare, al momento è necessario prelevare il seguente sorgente dal SVN: p4a_db.php rev. 1762 che risolve un problema relativo agli apici usati nelle query. In alternativa si deve aspettare il nuovo imminente rilascio.
Infine, la funzione code>P4A_Output_File() genera un file temporaneo nella directory /uploads/tmp che quindi deve essere presente e avere i permessi di scrittura. Purtroppo non ho ancora capito perché i file temporanei non vengano eliminati alla fine della sessione, come mi sarei aspettato. Indagherò su questa anomalia, nel frattempo bisogna eliminarli manualmente per evitare che la directory si riempia di pdf!

Riferimenti ed approfondimenti:

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= & $this->build("dettaglio_ordine", "dett_ordine");
$dett_ordine->dettaglio_ordine->addFilter("ordini_idordini = ?", 
                                      $this->ordini->fields->idordini); 
$this->dett_ordine->dettaglio_ordine->firstRow();
 
//Build component detail source
$cmpntidtglo= & $this->build("componenti_dettaglio", "cmpntidtglo");    
$cmpntidtglo->componenti_dettaglio->addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this->dett_ordine->dettaglio_ordine->fields->iddettaglio_ordine); 
$cmpntidtglo->componenti_dettaglio->firstRow();
 
//Build accessories detail source
$accesrdtglo= & $this->build("accessori_dettaglio", "accesrdtglo");
$accesrdtglo->accessori_dettaglio->addFilter("dettaglio_ordine_iddettaglio_ordine = ?", 
                      $this->dett_ordine->dettaglio_ordine->fields->iddettaglio_ordine); 
$accesrdtglo->accessori_dettaglio->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=& $this->build("p4a_frame", "frm");
$frm->setWidth(800);
// Frame component
$frmcmpntidtglo=& $this->build("p4a_frame", "frmcmpntidtglo");
$frmcmpntidtglo->setWidth(700);
$frmcmpntidtglo->anchor($cmpntidtglo->table_componenti_dettaglio); 
$frmcmpntidtglo->anchor($cmpntidtglo->tool);
$frmcmpntidtglo->anchor($cmpntidtglo->frm);  
// Frame accessories
$frmaccesrdtglo=& $this->build("p4a_frame", "frmaccesrdtglo");
$frmaccesrdtglo->setWidth(700);
$frmaccesrdtglo->anchor($accesrdtglo->table_accessori_dettaglio); 
$frmaccesrdtglo->anchor($accesrdtglo->tool);
$frmaccesrdtglo->anchor($accesrdtglo->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=& $this->build("p4a_fieldset", "fsetShowHide");
$fsetShowHide->setWidth(700);
$fsetShowHide->setTitle("Visualizzazione");
$array_source=& $this->build("P4A_Array_Source", "array_source");
$a = array(); 
$a[] = array('id'=>1, 'value'=>'nascondi tutto'); 
$a[] = array('id'=>2, 'value'=>'dettaglio componenti');
$a[] = array('id'=>3, 'value'=>'dettaglio accessori');
$a[] = array('id'=>4, 'value'=>'mostra tutto');
$array_source->setPk('id'); 
$array_source->load($a); 		
$combo_showhide =& $this->build("p4a_field","combo_showhide");
$combo_showhide->setType("select");
$combo_showhide->setSource($array_source);
$combo_showhide->setLabel("Mostra");
$fsetShowHide->anchor($combo_showhide);
//.......

Intercettiamo l’evento change del combo box

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

Ancoriamo tutto nel secondo tab:

1
2
3
4
5
6
7
8
//.......
$tab2->anchor($fsetShowHide);
$tab2->anchor($dett_ordine->frm);  
$tab2->anchor($frmcmpntidtglo);
$tab2->anchor($frmaccesrdtglo);
$frmcmpntidtglo->setVisible(FALSE);    
$frmaccesrdtglo->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->dett_ordine->dettaglio_ordine->fields->
                                iddettaglio_ordine->getValue();
    // Cannot show accessories or components tables if thera are no records in
    // models table
    if (empty($idDettOrd))
        {
          $res=1; // Hide all
          $comboValue=$this->combo_showhide->getNewValue();
          if($comboValue>1)
          {
          $this->dett_ordine->message->setValue("Impossibile visualizzare 
                                      dettagli: nessun modello presente!");
          $this->combo_showhide->setValue(1); // Hide all                                           
          }                                           
        }
     // If there are records in models table
        else
        {
          $res=$this->combo_showhide->getNewValue();
        }
    switch ($res)
    {
      case 1: // Hide all
          $this->frmcmpntidtglo->setVisible(FALSE);
          $this->frmaccesrdtglo->setVisible(FALSE);      
      break;
      case 2: // Show components
          $this->frmcmpntidtglo->setVisible(TRUE);
          $this->frmaccesrdtglo->setVisible(FALSE);
      break;
      case 3: // Show accessories
          $this->frmcmpntidtglo->setVisible(FALSE);
          $this->frmaccesrdtglo->setVisible(TRUE);
      break;
      case 4: // Show all
          $this->frmcmpntidtglo->setVisible(TRUE);
          $this->frmaccesrdtglo->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:

P4A framework: utilizzo di multicheckbox e multiselect con tabelle di relazione N:N

screenshot della multicheckboxUn tipico esempio di utilizzo di un multicheckbox o di una multiselect si ha quando è necessario creare una maschera per una tabella di relazione N:N. Nel framework P4A è possibile implementare entrambi i tipi di oggetto.

Il problema è che, come detto in precedenti post, la documentazione non è completa, si rende necessario quindi fare una una ricerca nel forum. In questo thread e in quest’altro, viene spiegato il funzionamento. Sperando di fare cosa gradita, ho cercato di riassumere la procedura in questo esempio pratico.

 

schema del database Dunque, ammettiamo di avere tre tabelle relazionate come nella figura a fianco. L’obiettivo è di poter associare ad ogni fascia uno o più colori. Nella tabella di relazione possono essere presenti N fasce e per ogni fascia N colori. Per prima cosa creaiamo le tre tabelle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE `colori` (
  `idcolori` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `nome` VARCHAR(255) NOT NULL,
  `note` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY  (`idcolori`),
  UNIQUE KEY `colori_index_nome` (`nome`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;
-- -----------------------------------------------
CREATE TABLE `fasce` (
  `idfasce` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `descrizione` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY  (`idfasce`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1;
-- -----------------------------------------------
CREATE TABLE `fasce_colori` (
  `colori_idcolori` BIGINT(20) NOT NULL,
  `fasce_idfasce` BIGINT(20) NOT NULL,
  PRIMARY KEY  (`colori_idcolori`,`fasce_idfasce`),
  KEY `fasce_colori_FKIndex1` (`colori_idcolori`),
  KEY `fasce_colori_FKIndex2` (`fasce_idfasce`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

A questo punto è necessario creare le due classi e le maschere per la gestione delle tabelle fasce e colori nel modo standard, che non illustro ma sono presenti nel pacchetto scaricabile.
Possiamo finalmente creare la maschera per la tabella di relazione: fasce_colori nel seguente modo:

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
class P4A_Fasce_colori extends P4A_Mask
{
 function P4A_Fasce_colori()
 {
 $this->P4A_Mask();
 $p4a = & P4A::singleton();
 // DB Source
 $this->build("p4a_db_source","fasce");
 $this->fasce->setTable("fasce");
 $this->fasce->setPk("idfasce");
 //Filtro il record con idfasce=0 che indica
 //tutti i colori e non deve essere modificato
 $this->fasce->setWhere("idfasce > 0");
 $this->fasce->setPageLimit(15);
 $this->fasce->addMultivalueField("fld_fasce_colori",
  "fasce_colori","fasce_idfasce", "colori_idcolori");
 $this->fasce->load();
 $this->setSource($this->fasce);
 $this->setTitle('Gestione fasce colore');
 $this->fasce->firstRow();
 $this->build("p4a_db_source","colori");
 $this->colori->setTable("colori");
 $this->colori->setPk("idcolori");
 $this->colori->load();
 $this->fields->fld_fasce_colori->setType("multicheckbox");
 // Oppure se si preferisce la multiselect
 //$this->fields->fld_fasce_colori->setType("multiselect");
 $this->fields->fld_fasce_colori->setSource($this->colori);
 $this->fields->fld_fasce_colori->label->setVisible(false);
 //Fieldset con l'elenco dei campi
 $this->build("p4a_fieldset", "fset");
 $this->fset->setWidth(590);
 $this->fset->setTitle("Selezione colori");
 $this->fset->anchor($this->fields->fld_fasce_colori);
 // Toolbar
 $this->build("p4a_simple_toolbar", "toolbar");
 $this->toolbar->setMask($this);
 $this->toolbar->buttons->new->disable();
 // Table
 $table = & $this->build("p4a_table", "table");
 $table->setWidth(600);
 $table->setSource($this->fasce);
 $table->setVisibleCols(array("descrizione"));
 $table->showNavigationBar();
 // Message
 $message = & $this->build("p4a_message", "message");
 $message->setWidth("300");
 // Frame
 $frm= & $this->build("p4a_frame", "frm");
 $frm->setWidth(600);
 $frm->anchorCenter($message);
 $frm->anchorCenter($table);
 $frm->anchorCenter($this->fset);
 // Display
 $this->display("main", $frm);
 $this->display("menu", $p4a->menu);
 $this->display("top", $this->toolbar);
 }
}

Notate che la sorgente dati principale è quella della tabella dove agisce il selettore dei record,
nel nostro caso: fasce. L’azione chiave di tutta la procedura è la costruzione
di un campo attraverso il metodo addMultivalueField(),
i quattro parametri passati nell’ordine a questo metodo sono:

  • Il nome del nuovo campo
  • Il nome della tabella di relazione
  • Il nome della chiave esterna che lega la tabella dove agisce il selettore record
  • Il nome della chiave esterna che lega la tabella che funge da sorgente dati per il nuovo campo

Richiamando il metodo saveRow() le query di aggiornamento e inserimento verranno automaticamente eseguite dal P4A framework engine senza dover scrivere ulteriori righe di codice!

Download:

Il pacchetto completo è scaricabile qui

Conclusioni:

Il setup del codice necessario all’utilizzo di multicheckbox e multiselect è semplice ma poco documentato, il suo utilizzo è potente e fa risparmiare parecchio tempo.

Riferimenti ed approfondimenti: