Oggi ho dovuto impiegare un pò di tempo per risolvere un problema di compatibilità cross-browser di un cliente, relativo a due elementi HTML <select> utilizzati per consentire all'utente di selezionare regione e provincia. Le select funzionavano nel seguente modo:
- ddlRegioni: contenente un elenco di regioni italiane, con possibilità di selezionare "tutte le regioni" (default).
- ddlProvince: contenente un elenco di tutte le province italiane, raggruppate per regioni all'inetrno di appositi <optgroups>, con possibilità di selezionare "Tutte le province" (default).
le due caselle a discesa erano collegate da un semplice ma efficace script JQuery che aveva il compito di svolgere le seguenti operazioni: quando l'utente seleziona una regione all'interno della select ddlRegioni, lo script fa in modo di "nascondere" tutti gli <optgroups> presenti all'interno della select ddlProvince che non corrispondono al nome della regione selezionata, ovvero tutti tranne uno: in questo modo la seconda select risulta "filtrata", lasciando all'utente la possibilità di selezionare una delle province di quella regione oppure "Tutte le province".
Questo è il codice HTML completo, diviso in 3 parti per comodità:
- ddlRegioni - ovvero l'elemento select mediante il quale viene impostato il filtro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<select id="ddlRegioni" name="ddlRegioni"> <option value="">Tutte le regioni</option> <option value="14">ABRUZZO</option> <option value="18">BASILICATA</option> <option value="19">CALABRIA</option> <option value="16">CAMPANIA</option> <option value="9">EMILIA ROMAGNA</option> <option value="7">FRIULI VENEZIA GIULIA</option> <option value="13">LAZIO</option> <option value="8">LIGURIA</option> <option value="3">LOMBARDIA</option> <option value="12">MARCHE</option> <option value="15">MOLISE</option> <option value="22">NESSUNA (ESTERA)</option> <option value="1">PIEMONTE</option> <option value="17">PUGLIA</option> <option value="21">SARDEGNA</option> <option value="20">SICILIA</option> <option value="10">TOSCANA</option> <option value="5">TRENTINO ALTO ADIGE</option> <option value="11">UMBRIA</option> <option value="2">VALLE AOSTA</option> <option value="6">VENETO</option> </select> |
- ddlProvince - ovvero l'elemento select che viene "filtrato" al seconda della scelta effettuata sul precedente ddlRegioni:
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 |
<select id="ddlProvince" name="ddlProvince"> <option value="">Tutte le province</option> <optgroup label="ABRUZZO"> <option value="24">CH - CHIETI</option> <option value="42">AQ - L'AQUILA</option> <option value="66">PE - PESCARA</option> <option value="88">TE - TERAMO</option> </optgroup> <optgroup label="BASILICATA"> <option value="52">MT - MATERA</option> <option value="71">PZ - POTENZA</option> </optgroup> <optgroup label="CALABRIA"> <option value="23">CZ - CATANZARO</option> <option value="26">CS - COSENZA</option> <option value="28">KR - CROTONE</option> <option value="75">RC - REGGIO DI CALABRIA</option> <option value="101">VV - VIBO VALENTIA</option> </optgroup> <optgroup label="CAMPANIA"> <option value="8">AV - AVELLINO</option> <option value="11">BN - BENEVENTO</option> <option value="21">CE - CASERTA</option> <option value="56">NA - NAPOLI</option> <option value="81">SA - SALERNO</option> </optgroup> <optgroup label="EMILIA ROMAGNA"> <option value="14">BO - BOLOGNA</option> <option value="31">FE - FERRARA</option> <option value="34">FC - FORLI'-CESENA</option> <option value="55">MO - MODENA</option> <option value="62">PR - PARMA</option> <option value="67">PC - PIACENZA</option> <option value="74">RA - RAVENNA</option> <option value="76">RE - REGGIO NELL'EMILIA</option> <option value="78">RN - RIMINI</option> <option value="104">RS - SAN MARINO</option> </optgroup> <!-- rest of the optgroupts skipped - there are 20 in totals --> </select> |
- Lo script JQuery che si occupa della logica di filtering:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var $sR = $("select#ddlRegioni"); var $sP = $("select#ddlProvince"); $(function () { function updateProvinceList() { var regName = $sR.find("option:selected").text(); if ($sR.val() > 0) { $sP.find("optgroup:not([label='" + regName + "'])").hide(); $sP.find("optgroup[label='" + regName + "']").show(); if ($sP.find("option:selected").not(":visible")) { $sP.val(""); } } else { $sP.find("optgroup").show(); } } $("select#ddlRegioni").on("change", function (e) { updateProvinceList(); }); }); |
Il problema
Il codice mostrato sopra funziona molto bene su Chrome e Firefox... ma, sfortunatamente, non su Internet Explorer. Il motivo è presto detto: IE, che come ben sa chi sviluppa in HTML/CSS/JavaScript non è particolarmente rispettoso degli standard, non esclude dalla visualizzazione gli elementi <optgroup> nascosti tramite JQuery, in quanto l'attributo di stile display:none non viene rispettato per quell'elemento: di conseguenza, sia l'elemento che il suo contenuto - ovvero, tutte le province di tutte le regioni non selezionate - restano visibili all'interno della casella a discesa.
La soluzione
Per risolvere il problema esistono numerosi workaround pubblicati sul sito StackOverflow e altri portali similari. Alcuni utenti suggeriscono di utilizzare appositi plugin JQuery esterni realizzati con il preciso scopo di risolvere questo problema (come ad es. l'ottimo Toggle Dropdown Options plugin): ho preferito non adottare questa soluzione per evitare di appesantire il mio codice con troppi script, preferendo orientarmi su alternative che utilizzassero JQuery standard.
Mi sono quindi imbattuto in alcune discussioni in cui veniva suggerito l'utilizzo del metodo jQuery.remove()al posto di jQuery.hide(), che ha una compatibilità cross-browser pressoché totale: una soluzione tutt'altro che ideale nel mio caso, visto che non potevo permettermi di eliminare a titolo permanente gli elementi <optgroup> in quanto l'utente avrebbe potuto cambiare più volte la regione selezionata.
Alla fine ho realizzato il seguente script, che utilizza una combinazione di jQuery.remove() e jQuery.clone() riuscendo così ad ottenere il risultato che mi serviva:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var $sR = $("select#ddlRegioni"); var $sP = $("select#ddlProvince"); var $sPclone = $sP.clone(); function updateProvinceList() { var regName = $sR.find("option:selected").text(); if ($sR.val() > 0) { $sP.find("optgroup").remove(); $sP.append($sPclone.find("optgroup[label='" + regName + "']").clone()); } else { $sP.find("optgroup").remove(); $sP.append($sPclone.find("optgroup").clone()); } } $("select#ddlRegioni").on("change", function (e) { updateProvinceList(); }); |
Lo script utilizza solo JQuery "vanilla" ed è compatibile con Chrome, Firefox, IE, Edge e qualsiasi altro browser. La logica di funzionamento è piuttosto semplice: viene creato un clone della select ddlRegioni, che viene poi utilizzato come "base dati" per caricare all'interno della ddlProvince l'<optgroup> corrispondente alla regione selezionata... oppure tutti gli <optgroup> se viene selezionata l'opzione "tutte le regioni".
Conclusioni
Per il momento è tutto: spero che questo articolo possa essere d'aiuto agli sviluppatori JavaScript alla ricerca di una soluzione "JQuery vanilla" per risolvere questo problema. Felice sviluppo!
Riferimenti
- Jquery hide/show optgroup base on main selector (StackOverflow)
- Hiding Options of a Select with JQuery (StackOverflow)
- Hiding Option Groups (OPTGROUPS) in Chrome and Internet Explorer with JQuery
- Cross-Browser JQuery hidden optgroup