Risorse
guigen: constraint dichiarativi per widget e menu
Dalla versione 3.2.0 di guigen è disponibile una nuova funzionalità per la gestione delle dinamiche di user interface: i DeclarativeUIConstraints. Vediamo di cosa si tratta.
Fino alle versioni precedenti di guigen per determinare se un Widget o una voce di menu debba essere abilitato/disabilitato - mostrato/nascosto esistevano tre differenti strumenti:
- i comandi ONOFFCommand e VisibilityCommand che, inseriti in una catena di comandi (ad esempio associata ad un evento di UI oppure ad un CPCommand tipo on-enter, on-init, ...) permettono di comandare la modifica dello stato del widget
- gli ScreenState che permettono di definire uno "stato" della schermata e, per ogni stato, quali widget devono essere abilitati e resi visibili (il passaggio ad uno stato viene effettuato tramite lo ScreenStateCommand)
- i SecurityConstraints che, associati ai Widget e alle voci di Menu, permettono di definire delle condizioni di abilitazione legate ai permessi che l'utente collegato possiede
Le prime due soluzioni, i comandi e gli ScreenState, permettono di adeguare le schermate allo stato dell'applicazione, secondo le logiche definite caso per caso, mentre il terzo strumento (i constraint di sicurezza) non è legato allo stato dell'applicazione ma alle caratteristiche dell'utente.
Per quanto riguarda i primi due strumenti è interessante notare che, se da un lato la possibilità di gestire direttamente nel modello lo spegnimento/accensione di un Widget rappresenta uno strumento molto valido per realizzare schermate che si adattano ai dati e allo stato dell'applicazione, dall'altro lato la caratteristica "imperativa" dei comandi pone due problemi di cui occorre farsi carico:
- decidere quando applicare un determinato comando.
- decidere come e quando ristabilire la situazione pregressa all'esecuzione del comando
I casi di applicazione dei comandi di ONOFF e Visibility sono molteplici, ma è utile focalizzarsi su un esempio specifico per comprenderne meglio i limiti.
Esempio: schermata di inserimento/modifica della scheda di un soggetto, contenente, tra gli altri campi, tre ComboBox associate a regione, provincia e comune di residenza. La richiesta è quella di gestire la selezione dei tre campi a cascata (ovvero prima si sceglie la regione e le altre due combo sono invisibili, una volta scelta la regione la combo della provincia diventa visibile e selezionabile , e così via).
Concentriamoci principalmente su come sia possibile implementare questo meccanismo di cascata con gli strumenti a disposizione prima della versione 3.2.0, per poi analizzare come i DeclarativeUIConstraint possono rendere più agevole il compito.
Le possibilità tramite comandi sono fondamentalmente due:
- associare i comandi di visibilità delle combo successive all'evento di selezione sulla combo n-esima: a fronte dell'evento di selezione, a seconda che la selezione sia stata effettuata o annullata, si procederà a richiamare gli appositi comandi di visibilità sulle combo successive
- associare i comandi di visibilità agli eventi impliciti di AFTER_EVENT, tramite CPCommand: associando all'evento di refresh una logica che verifichi il valore di selezione della combo è possibile poi far eseguire gli appositi comandi di visibilità sulle combo successive.
Entrambe le soluzioni hanno almeno un problema: come si fa a ripristinare la configurazione corretta quando si rientra nella schermata e dunque non vengono più scatenati gli eventi?
In entrambi i casi una possibile soluzione è rappresentata dall'inserimento della logica di impostazione corretta delle combo a fonte dell'evento implicito di ON_ENTER: a seconda dello stato delle informazioni (regione/provincia/comune) dell'oggetto associato ai widget della schermata devono essere richiamati gli appositi comandi di visibilità (es. se è solo presente l'informazione della regione, allora devono essere rese invisibili le combo di provincia e comune, per ristabilire l'invariante).
In pratica questo comporta in entrambe le casistiche una duplicazione della gestione delle combo, che rappresenta una complicazione nella manutenzione dell'applicazione.
la nuova soluzione tramite DeclarativeUIConstraints
Una soluzione più adeguata ed intuitiva per la gestione di questa casistica sarebbe quella di definire una espressione logica, il cui valore di verità è basato sullo stato (dati) dell'applicazione, ed associarla al Widget, come constraint per la visibilità / abilitazione. Questo è esattamente ciò che permette di fare il DeclarativeUIConstraint.
Nell'esempio in questione sarebbe possibile semplicemente dichiarare che la combo "provincia" deve essere resa visibile solo se il campo "regione" dell'oggetto "soggetto" mantenuto in sessione risulta valorizzato, con una espressione che risulterebbe simile alla seguente:
model.appDatasoggetto != null && model.appDatasoggetto.residenza != null && model.appDatasoggetto.residenza.regione != null
In questo modo non è necessario preoccuparsi del "quando" accendere/spegnere i widget, in quanto la condizione di visibilità verrà veriificata ad ogni rendering della schermata.
Allo stato attuale le espressioni dei DeclarativeUIConstraint devono essere scritti nel linguaggio previsto dalla libreria beanshell, con i seguenti accorgimenti:
- il constraint deve essere un espressione che ha come valore un valore booleano (non è possibile utilizzare statement differenti e/o definire variabili locali)
- all'interno dell'espressione è possibile referenziare il model della schermata e l'oggetto "session". In particolare se il ContentPanel referenzia un ApplicationData di nome "soggetto", allora esso sarà referenziabile almeno nei seguenti modi:
- model.appDatasoggetto // accesso con la convenience syntax di beanshell per l'accesso ai javabeans
- model.getAppDatasoggetto() // accesso standard tramite getter
- session.get("soggetto") // in struts2/spring la sessione è vista come comune Map
- session{"soggetto"} // accesso in modalità hash-map
- nell'espressione è possibile richiamare metodi static di classi di utilità , purchè siano referenziate in modo completamente qualificato; al metodo è possibile passare come parametri sia la session che model (es. it.csi.prodotto.business.MyConstraintUtils.verificaEtaSoggetto(model.appDatasoggetto.datiAnagrafici.eta) )
- nell'accesso a sotto-elementi del model (o della session) è necessario gestire la possibilità che le componenti dei bean siano nulle (nell'esempio di sopra è stato infatti necessario verificare che l'application data prima e la property "residenza" poi, fossero valorizzate, prima di testare il valore della property "regione", pena un potenziale errore a runtime in fase di rendering della schermata se gli oggetti non sono valorizzati