mercoledì 1 luglio 2009

Magnolia, custom control, FreeMarker e Javascript

Riporto, tralasciando bestemmie e dati veritieri, una soluzione trovata per un custom control in Magnolia.

Problema
Creare un custom control in Magnolia CMS con due select, la prima con tre option e la seconda con una decina di option, che però non devono essere mostrate tutte, ma solo quelle in relazione con la option selezionata nella prima select.

Soluzione (la mia :-))
Ho creato un custom control utilizzando un template FreeMarker per la prima select, mentre per la seconda un controllo select standard.
Nel template FreeMarker, mi sono inventato a colpi di Javascript, la creazione della seconda select, tenendo conto anche dell'eventuale precedente scelta dell'utente.
Ho risolto con un dizionario chiave valore per le option della seconda select e una serie di array con i valori selezionabili in base alla scelta della prima select... chiaro no?
Un po' di codice per capire meglio, di seguito il dizionario e gli array:

var secondaSelect = { "opt1": "Opzione 1",
"opt2": "Opzione 2",
"opt3": "Opzione 3",
"opt4": "Opzione 4",
"opt5": "Opzione 5",
"opt6": "Opzione 6",
"opt7": "Opzione 7",
"opt8": "Opzione 8",
"opt9": "Opzione 9",
"opt10": "Opzione 10" };

var OptionXPrimaSelect_0 = [ "opt1", "opt2", "opt5", "opt7" ];

var OptionXPrimaSelect_1 = [ "opt2", "opt4", "opt6", "opt8", "opt10" ];

var OptionXPrimaSelect_2 = [ "opt3", "opt6", "opt9" ];


Poi ho creato delle funzioni di init e popolamento per le select:


// add option
function addOption(select, option)
{
try {
select.add(option, null); // standards compliant; doesn't work in IE
}
catch(ex) {
select.add(option); // IE only
}
}

// remove all option
function removeAllOptions(select)
{
for(var i = select.childNodes.length-1; i>=0; i--)
{
select.remove(select.childNodes[i]);
}
}

// init: reset select
function _initOptions(item, listOptions)
{
removeAllOptions(item);
for (var o in listOptions)
{
var opt = document.createElement('option');
opt.text = listOptions[o];
opt.value = o;
addOption(item, opt);
}
}


La funzione all'onchange per la prima select (con codice chiaramente intrusivo), infatti la select è tipo <select id="mySelectId" onchange="changeSelect">...


function changeSelect(option, index)
{

var mySelect;

mySelect = document.getElementById("firstSelect");

if (index === 0)
{
_initOptions(mySelect, secondaSelect);
}

if (option === "uno")
{
showOptions(mySelect, OptionXPrimaSelect_0);
}
else if (option === "due")
{
showOptions(mySelect, OptionXPrimaSelect_1);
}
else if (option === "tre") // else :-)
{
showOptions(mySelect, OptionXPrimaSelect_2);
}
}

Manca la funzione showOptions, il cardine di questo template FreeMarker:


function showOptions(item, optionsToDisplay)
{
var optionsSelect = item.childNodes;
var optionTag;
var find = false;

// Array contenente le option da visualizzare
var newSelectOptions = [];
// Option selezionata, se arrivo da un edit
var selectedValue = "";
for (var i = 0; i < optionsSelect.length; i++)
{
optionTag = optionsSelect[i];
if (optionTag.selected)
{
selectedValue = optionTag.value;
}

for (var j = 0; j < optionsToDisplay.length; j++)
{
if (optionTag != null && optionTag != "" && optionTag.value === optionsToDisplay[j])
{
find = true;
}
}
if (find === true && optionTag != null)
{
newSelectOptions.push(optionTag);
}
find = false;
}

removeAllOptions(item);

// add child
var selected = 0;
var newOpt;
for (var i = 0; i < newSelectOptions.length; i++)
{
newOpt = newSelectOptions[i];
addOption(item, newOpt);
if (newOpt.value === selectedValue)
{
selected = i;
}
newOpt = null;
}
item.selectedIndex = selected;
}


Si noti che utilizzo una variabile per salvare la option selezionata, per poi settare la proprietà selectedIndex.
Infatti, anche se gli oggetti presenti in 'newSelectOptions' sono tutte option, con IE non sono riuscito a leggere l'attributo selected (se presente) e un'eventuale verifica (es. elemento.selected === 'selected', oppure elemento.selected, o (!!((elemento || {}).selected)) ... ecc ecc ) restituisce sempre true, quindi inutilizzabile :-); mentre verificando l'eguaglianza tra le value, sono riuscito a recuperare la option selezionata.
Infine la funzione per caricare tutta questa accozzaglia di Javascript e HTML:


(function doSelectedOptions() {
var oldonload = window.onload;
if(typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
var item = document.getElementById("mySelectId");
changeSelect(item.value);
}
}
})();



Tutto questo post solo per farmi insultare dal buon Calde... ;-), gliel'avevo promesso! Ma anche per condividere con il mondo qualcosa... forse utile... forse no :-).

Domande dubbi perplessità, chiedete e vi sarà dato... sempre se mi è dato sapere (quanto è buono l'equino con le pere (dixit)).

Chiaramente ho tralasciato la creazione dei dialoghi, paragrafi ecc... l'idea era quella di condividere la soluzione e di farmi insultare, chiaro! :-P

Nota a margine... sì, funziona con IE6... per ora!

Sani.

Risorse:
http://stackoverflow.com/questions/386094/javascript-options-selected-ie6-vs-ff2
Magnolia CMS
FreeMarker

1 commento:

S... ha detto...

Un esempio pratico, per chiarezza:
la prima select contiene tutte le regioni e la seconda tutte le province italiane.
All'init vengono caricate tutte le option e all'onchange della select delle regioni, vengono eliminate le province che non ti interessano (mantenendo l'attributo selected se in edito mode, altrimenti è la prima di default).

Quindi il Javascript sarebbe molto prolisso (dizionario con tutte le regioni e array di matching), ma è solo un esempio.

Nel mio caso mi servivano ordinamenti per degli item, quindi molto meno d'impatto :-).