Supporto volontario e collaborativo per Joomla!® in italiano

Select list dipendenti con Joomla 1.6

Informazioni utiliLe select dinamiche sono da sempre uno dei problemi piu' ostici con cui i programmatori php hanno a che fare.
Molte volte abbiamo due select list legate fra di loro: al variare della prima, devono essere caricati valori differenti nella seconda; un esempio pratico è il caso delle province - comuni.
Nel caso quindi , volessimo sviluppare un componente Joomla con queste simpatiche select abbiamo da buttar giu'  un po' di codice javascript e qualche riga di PHP per ottenere un risultato efficiente e veloce.

Il buon tampe125, qualche mese fa aveva inserito una bella guida relativa alla versione 1.5, ma nel frattempo la release si e' evoluta e siamo ormai nell'era della 1.6 e 1.7, che e' tanto bella, ma con il codice del nostro (sigh) non funziona piu'.
Non funziona piu' perche' mootools (il framework javacript inglobato nel nostro CMS preferito) e' upgrdato anch'esso dall'ormai obsoleto 1.1 alla 1.2 cambiando la sintassi di alcune funzioni basilari per il nostro script.

Ho quindi creato questa nuova guida alla quale faro' riferimento a larghe mani al vecchio (encomiabile) lavoro del mio predecessore, cercando ove e' possibile di miglorare qua e la' spiegazioni e funzionalita'.

Passiamo subito ai fatti.
Io avevo l'esigenza di creare select list dipendenti, come nel classico caso di province->comuni.
In più, il codice che le generava doveva essere dinamico a sua volta, perchè i campi cambiavano nome a seconda di dove mi trovavo.

Il risultato che volevo ottenere (ed ho ottenuto) è stato questo:



Il sistema si compone di 3 parti:
  • la funzione che costruisce al volo il codice javascript (chiamata JSlinkedSelect)
  • la funzione che elabora la richiesta Ajax e resituisce i valori per la select dipendente (chiamata json_select, dentro al controller del mio componente)
  • il codice necessario per far partire il tutto

Il codice javascript

Partiamo dalla generazione del codice javascript;
Io questa sezione di codice l'ho messa nella cartella views del mio componente come function del file
view.html.php

anche in questo caso possiamo dividerlo in due:
  • una parte generale che si occuperà di processare e inserire i nuovi valori nella select dipendente
  • la parte ajax vera e propria, inclusa in una seconda funzione

JSlinkedSelect si aspetta un array di array, perchè in una pagina potrei avere più di una select list dipendente.
Le chiavi dell'array "più interno" sono le seguenti:
$ele['padre']   = elemento "padre". al cambiare del suo valore il figlio caricherà i valori legati
$ele['figlio']     = elemento "figlio".
$ele['table']    = tabella dove si trovano i valori per il figlio.
$ele['where']  = campo di collegamento (join) fra il padre e il figlio.

	function JSlinkedSelect($select){

$js = "window.addEvent('domready', function(){
var base_url = 'index.php?option=com_gestass&task=json_select&format=raw';

function buildSelect(select, options)
{
var select = $(select);

select.empty();

options.each(function(item) {
var option = new Element('option', {
value: item.value.toString()
});


option.set('html', (item.text.toString()));

option.injectInside(select);
});
}";
foreach($select as $ele){
$js .= $this->_buildAddEvent($ele);
}

$js .= "});";

return $js;
}
Di questa funzione non c'è molto da dire; viene creata il codice JS che si occuperà di "svuotare" il figlio e di inserire le opzioni prendendole dalla risposta in formato JSON.
Il grosso del lavoro viene svolto da buildAddEvent()
function _buildAddEvent($ele){

$js = "

$('{$ele['padre']}').addEvent('change', function(){

if($('{$ele['figlio']}_wait')) $('{$ele['figlio']}_wait').setStyle('display', 'inline');

var req_url = base_url + '&id=' + this.value + '&table={$ele['table']}&where={$ele['where']}
&figlio={$ele['figlio']}';

var JSonReq = new Request.JSON({url : req_url,
onComplete: function(response){
buildSelect('{$ele['figlio']}', response);
if($('{$ele['figlio']}_wait')) $('{$ele['figlio']}_wait').setStyle('display', 'none');
}
}).POST();
});

";

return $js;
}

Per i curiosi , rispetto alla vecchia versione abbiamo modificato le istruzioni
 option.set('html', (item.text.toString()));

che in origine era

option.setHTML(item.text.toString());

e l'istruzione

var JSonReq = new Request.JSON({url : req_url,
onComplete: function(response){
buildSelect('{$ele['figlio']}', response);
if($('{$ele['figlio']}_wait')) $('{$ele['figlio']}_wait').setStyle('display', 'none');
}
}).POST();

che in origine recitava:

var JSonReq = new Json.Remote(req_url, {
onComplete: function(response){
buildSelect('{$ele['figlio']}', response);
if($('{$ele['figlio']}_wait')) $('{$ele['figlio']}_wait').setStyle('display', 'none');
}
}).send();

Ok, facciamo un po' di chiarezza.
Ogni volta che il padre cambierà valore, viene lanciata la relativa funzione.

Dopodichè vengono composti i parametri della richiesta, quando verrà completata, la risposta sarà processata e inserita nella select figlia.
Andiamo a vedere come viene elaborata la richiesta

Il codice PHP


Nel  controller del componente (per intenderci il file "component.php" inserisco questa function  che si occupa di gestire la chiamata e restituire i valori richiesti nel formato JSON.

function json_select(){
$database = &JFactory::getDBO();
$id = JRequest::getCmd('id');
$join = JRequest::getCmd('where');
$table = JRequest::getCmd('table');
$figlio = JRequest::getCmd('figlio');
$padre = JRequest::getCmd('padre');

if((int)$id){
$where = " $join = $id";
}else{
$where = " $join = ".$database->Quote($database->getEscaped($id, true), false);
#escape + quote della stringa

}
$query = "SELECT {$id} as id, {$figlio} as descr FROM {$table} WHERE 1 = 1";
if($where)$query .= " AND $where";
$query .= " GROUP BY {$figlio} ORDER BY {$figlio} ";

$database->setQuery($query);
$rows = $database->loadObjectList();

$response[] = array('value' => '', 'text' => ' - Seleziona - ');
foreach($rows as $row){
$t['value'] = $row->descr;
$t['text'] = $row->descr;
$response[] = $t;
}
//echo $response;
echo json_encode($response);
}



Come prima cosa prendo i valori necessari per costruirmi la select (id, campo di join ,padre,  figlio e la tabella interessata). L'id può essere numerico oppure stringa, per cui in quest'ultimo caso effettuo l'escape e il quote.
La query si basa su una convenzione personale, in cui tutti i campi padre vengono rinominati id e tutti i campi figlio rinominati descr
Una volta caricata la query, organizzo i dati nello standard di Joomla e poi restituisco il tutto a video.

IL codice nella view!


Questa invece e' la sezione di codice da infilare sempre nella cartella views del nostro componente, nel file che si occupa di generare l'html (la classica default.php o form.php a seconda di come avete scritto il tutto, nel mio caso e' form.php) 
( vi suggerisco di infilarla in alto ben visibile magari appena sotto l'istruzione html in cui avete dichiarato il form)
$select[] = array('padre' => 'provincia', 'where' => 'provincia', 'table' => 'jos_comuni', 'figlio' => 'comune'    );
$select[] = array('padre' => 'comune', 'where' => 'comune', 'table' => 'jos_comuni', 'figlio' => 'cap');

$js = $this->JSlinkedSelect($select);

Questa serie di funzioni restituisce tutto il codice javascript necessario per avere due select list dipendenti, dopodichè è sufficiente aggiungerlo al documento tramite la funzione

$doc =& JFactory::getDocument();
$doc->addScriptDeclaration($js);

A questo punto non dobbiamo fare altro che creare in html i  due campi provincia e comune che interagiranno tra loro, niente di piu facile:

<tr>
<td width="300" align="right">
<label for="provincia">
<?php echo JText::_( 'Provincia' ); ?>:
</label>
</td>
<td>
<?php

$database = &JFactory::getDBO();
$database->setQuery("SELECT * FROM jos_comuni GROUP BY provincia");
$catastali = $database->loadObjectList();
foreach ( $catastali as $cat ) {
$options_province[] = JHTML::_( 'select.option', $cat->provincia, $cat->provincia );
}
$list_province =& JHTML::_( 'select.genericlist', $options_province, 'provincia', null, 'value', 'text', 'MI', 'provincia' );
echo $list_province;



?>
</td>
<tr>
<td width="300" align="right">
<label for="citta">
<?php echo JText::_( 'Citta' ); ?>:
</label>
</td>
<td>
<?php
$database = &JFactory::getDBO();
$database->setQuery("SELECT * FROM jos_comuni WHERE comune = 'SAN DONATO MILANESE' GROUP BY comune");
$catastali = $database->loadObjectList();
foreach ( $catastali as $cat ) {
$options_citta[] = JHTML::_( 'select.option', $cat->comune ,$cat->comune  );
}
$list_citta =& JHTML::_( 'select.genericlist', $options_citta, 'comune', null, 'value', 'text', 'SAN DONATO MILANESE', 'comune' );
echo $list_citta; 

?>  
</td>


Nel mio caso la tabella con province e comuni si chiama jos_comuni e preparo le due select con dei valori standardi (MI e SAN DONATO MILANESE) che in questo caso ho blindato nel codice ma che posso tranquillamente leggere da variabili a seconda delle mie esigenze.

E questo e' quanto, spero possa esservi utile, per dubbi o altro commentate l'articolo. Il mio blog www.gigorgio.com
CommentaCommenta questo articolo sul forum