Vorrei 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.
La 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();
//....... |
//.......
//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);
//....... |
//.......
// 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);
//....... |
//.......
//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");
//....... |
//.......
// 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);
//....... |
//.......
$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;
}
}
//....... |
//.......
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: