FAQ: dinamiche di user interface

In questa sezione sono riportate le domande più frequenti emerse durante l' utilizzo di guigen relative alle tecniche di modellazione delle dinamiche della user interface.

Q: devo progettare la componente dinamica dell'interfaccia utente di un progetto realizzato con guigen: dove posso trovare una descrizione degli eventi di user interaction modellabili sui vari widget/elementi?

A: L'elenco dei possibili eventi associabili ai vari widget è riportato nel catalogo dei pattern u.i (nelle sezioni relative ai vari Widget.

Per semplicità riportiamo di seguito i principali eventi associabili ai vari widget:


Q: E' possibile far scatenare una azione a fronte della selezione di una riga di una Table (ovvero alla selezione del check/radio posto sulla sinistra della tabella)?

A: No, attualmente questo non è un evento previsto. La modalità consigliata per realizzare azioni su una (o più) righe di una Table è tramite predisposizione di una pulsantiera al di sotto della tabella che preveda tutte le funzioni disponibili per quella tabella (metafora select & do).


Q: E' possibile far scatenare una azione a fronte della selezione/deselezione di un CheckBox?

A: No, attualmente questo non è un evento previsto. Utilizzare un widget RadioButtons con evento di tipo VALUE_CHANGED.


Q: Sto utilizzando su una Table la metafora di select & do come consigliato. I casi d'uso della mia applicazione prevedono una forte differenziazione delle azioni possibili a seconda della riga effettivamente selezionata. Esiste un modo per abilitare/disabilitare i pulsanti sottostanti a seconda della riga di tabella effettivamente selezionata?

A: No, attualmente questa possibilità non è prevista, poichè non è possibile modellare un evento di VALUE_CHANGED sulla Table. E' pertanto necessario gestire nella logica di business l'effettiva possibilità di eseguire l'azione specificata sul record selezionato, e di fornire l'adeguato feedback all'utente.


Q: Ho visto che per abilitare/disabilitare (mostrare/nascondere) dei Widget esistono tre possibilità:

  1. ONOFFCommand (VisibilityCommand)
  2. ScreenStateCommand.
  3. DeclarativeUIConstraint (da v. 3.2.0)

Quali sono le differenze e quali sono le casistiche di utilizzo delle tre modalità?

A:Una prima differenza deve rilevarsi tra prime due modalità e la terza: mentre infatti i primi due casi rappresentano comandi che devono essere esplicitamente eseguiti a fronte di un evento, il terzo caso rappresenta una condizione logica che deve essere verificata affinchè il widget sia visualizzato/abilitato. L'importanza di tale differenza sta nel fatto che i comandi devono essere eseguiti in un momento preciso e a fronte di quel comando cambia lo stato corrente del Widget, mentre nel caso del DeclarativeUIConstraint il criterio viene valutato ad ogni rendering e non muta lo stato del Widget.

Relativamente alle prime due modalità la differenza fondamentale tra le due modalità sta nel fatto che i comandi ONOFFCommand e VisibilityCommnad agiscono puntualmente su un insieme (anche ridotto) di widget e\o menu, mentre lo ScreenStateCommand permette di effettuare lo switch di ScreenState che rappresenta un set completo di abilitazioni/disabilitazioni dei widget contenuti nella schermata.

Da questa differenza sostanziale derivano le differenti casistiche di utilizzo:

La modalità del DeclarativeUIConstraint, invece, essendo fondata sulla definizione di criteri di abilitazione/visibilità basati sullo stato del model, risulta particolarmente utile in casistiche in cui l'abilitazione/visibilità o meno del Widget debba essere condizionata ai valori contenuti nel model, piuttosto che ad un particolare evento occorso nella UI. Se ad esempio un campo tfPartitaIVA sia da visualizzare solo se il campo "personaFisica: boolean" di un ApplicationData "soggetto" è valorizzato a false, allora risulta preferibile utilizzare un DeclarativeUIConstraint in cui si descriva tale criterio (es: [model.appDatapersona != null && model.appDatapersona.personaGiuridica == false]) piuttosto che accendere o spegnere il widget in modo comandato all'atto del caricamento del dato.


Q: Qual'è il livello di persistenza dello stato di visibilità/abilitazione dei widget? E' possibile resettare lo stato di default?

A: Lo stato di visibilità comandato (ovvero impostato tramite comandi ONOFF, Visibility, ScreenState) è mantenuto in sessione.

Allo stato attuale è possibile resettare lo stato di default solo agendo a basso livello ovvero modificando le strutture dati mantenute in sessione. In alternativa è possibile non utilizzare i meccanismi di default e gestire esplicitamente tutte le impostazioni.


Q: E' possibile impostare programmaticamente il tab (TabSetPanel) o lo step (WizardPanel) correntemente attivo?

A: Poichè TabSetPanel e WizardPanel sono sottoclassi del MultiPanel è possibile impostare l'item corrente di un TabSetPanel o di un WizardPanel nello stesso modo in cui è possibile fare con un MultiPanel.

Esiste il comando ActivateMultiPanelItem che, applicato ad un MultiPanel (o ad una delle due sottoclassi a tab o wizard) permette di rendere attivo uno specifico item tra quelli disponibili.

Il comando ActivateMultiPanelItem può essere modellato solo all'interno dello stesso ContentPanel che contiene il multipanel/tabset/wizard. Non è perciò possibile ad esempio comandare l'attivazione di un item alla pressione di un pulsante contenuto in un altra schermata o alla pressione di una voce di menu. Diventa per questo motivo spesso necessario inserire la logica di gestione dell'attivazione dei vari item nel metodo onRefresh della schermata.

Una tipica implementazione prevede le seguenti operazioni:

  1. modellare un ApplicationData destinato a contenere in maniera persistente l'indicazione relativa all'item correntemente selezionato (la codifica dell' informazione può essere di vario genere, numero, nome simbolico: dipende dalla logica che la utilizzerà) ed associarlo al ContentPanel che contiene il tabset/wizard/multi
  2. modellare nell onRefresh dello stesso ContentPanel un ExecCommand che avrà il compito di decidere (in base al valore dell'ApplicationData) quale sia l'item da visualizzare. Dovrà avere tanti CommandOutcome quanti sono i possibili item da abilitare: come comando associato a ciascun outcome occorre modellare un ActivateMultiPanelItem associato all'item i-esimo.


Q: Come posso nascondere un intero pannello a seconda della logica applicativa?

A: Per nascondere/mostrare un intero pannello a comando è necessario:

  1. inserire il pannello effettivo all'interno di un MultiPanelItem
  2. utilizzare il comando ActivateMultiPanelItem con activeItem = null per nascondere il pannello
  3. utilizzare il comando ActivateMultiPanelItem con activeItem = pannello da visualizzare per mostrare il pannello


Q: E' possibile evitare il caricamento di tutta la schermata a fronte di un evento di interazione utente, ed effettuare invece un refresh selettivo?

A: Si, è possibile, ma solo con la cartuccia neutral e abilitando le funzionalità ricche.

Per ottenere il refresh parziale della schermata corrente a fronte di un evento di interazione utente è necessario modellare al termine della catena di comandi associati all'handler di quell'evento un comando di RefreshViewCommand, nel quale è possibile specificare l'elenco dei pannelli e dei widget di cui si desidera effettuare il refresh al termine dell'esecuzione della logica associata all'evento.


Q: I requisiti utente prevedono la necessità di implementare la funzionalità di suggestion su un campo di testo. Ho letto nel catalogo dei pattern u.i che questa è una funzionalità disponibile, ma non ho capito come sia possibile attivarla.

A: La funzionalità di suggestion si attiva modellando sul TextField un EventHandler relativo all'evento KEY_PRESSED e implementando la logica di esecuzione di creazione delle voci suggerite, a fronte dell'immissione parziale nel campo di testo.

Più in dettaglio è necessario eseguire le seguenti operazioni:

  1. modellare un application data a scope SESSION di tipo array di stringhe destinato a contenere l'elenco di suggerimenti calcolato di volta in volta
  2. modellare un ApplicationDataBinding (collection-binding) sul TextField sul quale si vuole attivare la funzione di suggerimento
  3. modellare un EventHandler(KEY_PRESSED) sul TextField sul quale si vuole attivare la funzione di suggerimento
  4. associare al gestore di evento un unico comando di tipo ExecCommand: ad esso sarà associato il metodo java di business logic che implementerà la costruzione della lista di suggerimenti. L' ExecCommand dovrà avere una unica uscita possibile (es. "OK") con associato un NOPCommand
  5. nel metodo java associato all'ExecCommand implementare la logica di costruzione dell'elenco di suggerimenti. La query immessa dall'utente sarà contenuta nell ApplicationData associato al campo di testo (value-binding) mentre il risultato (elenco di suggerimenti da restituire al client) deve essere inserito nell'ApplicationData modellato al punto 1

Opzionalmente è possibile impostare il numero minimo di caratteri che l'utente deve digitare affinchè venga lanciata la richiesta allo strato server. Per fare questo è necessario impostare un elemento nell'attributo eventSpecifiers dell' EventHandler (KEY_PRESSED): il contenuto di tale specificatore di evento deve essere del formato: min_chars=n (dove n deve essere sostituito con il numero di caratteri prescelto). Il valore di default è di 4 caratteri.


Q: I requisiti utente prevedono la necessità di implementare la funzionalità di suggestion su una Combo-Box. Ho letto nel catalogo dei pattern u.i che questa è una funzionalità disponibile, ma non ho capito come sia possibile attivarla.

A: La funzionalità di suggestion si attiva modellando sulla ComboBox un EventHandler relativo all'evento KEY_PRESSED e implementando la logica di esecuzione di creazione delle voci suggerite, a fronte dell'immissione parziale nel campo di testo implicitamente associato alla combo.

La funzionalità è per certi versi analoga a quella attivabile sul TextField, ma vi sono alcune importanti considerazioni riguardanti le differenze tra ComboBox con e senza suggestion e le differenze tra TextField con suggestion e ComboBox con suggestion:

Più in dettaglio per implementare la suggestion su una ComboBox è necessario eseguire le seguenti operazioni:

  1. modellare un application data a scope SESSION destinato a contenere le opzoni selezionabili, esattamente come nel caso della ComboBox senza suggestion; modellare anche il collection-binding come di consueto.
  2. modellare un value-binding verso un ApplicationData che abbia come tipo il tipo degli elementi della ComboBox
  3. modellare come di consueto gli attributi keySelector e valueSelector
  4. modellare un EventHandler(KEY_PRESSED) sulla ComboBox sul quale si vuole attivare la funzione di suggerimento
  5. associare al gestore di evento un unico comando di tipo ExecCommand: ad esso sarà associato il metodo java di business logic che implementerà la costruzione della lista di suggerimenti. L' ExecCommand dovrà avere una unica uscita possibile (es. "OK") con associato un NOPCommand
  6. nel metodo java associato all'ExecCommand implementare la logica di costruzione dell'elenco di suggerimenti. La query immessa dall'utente sarà contenuta nell ApplicationData associato ala combo (value-binding), ma nella properietà referenziata nella ComboBox come keySelector, mentre il risultato (elenco di suggerimenti da restituire al client) deve essere inserito nell'ApplicationData modellato al punto 1

Opzionalmente è possibile impostare il numero minimo di caratteri che l'utente deve digitare affinchè venga lanciata la richiesta allo strato server. Per fare questo è necessario impostare un elemento nell'attributo eventSpecifiers dell' EventHandler (KEY_PRESSED): il contenuto di tale specificatore di evento deve essere del formato: min_chars=n (dove n deve essere sostituito con il numero di caratteri prescelto). Il valore di default è di 4 caratteri.


Q: e'possibile impedire il cambio del tab corrente a fronte, ad esempio, di un errore di validazione o di una particolare condizione di logica applicativa?

A: Si, è possibile. Occorre modellare un TabSwitcher, con EventHandler (CLICKED), con un ExecCommand che avrà il compito di eseguire le verifiche del caso.

Le condizioni per cui non verrà effettuato il cambio di tab sono:


Q: Ho la necessità di rendere invisibili/disabilitate alcune voci di menu a fronte dello stato applicativo: è possibile?

A: Non esiste un analogo per i menu dei comandi che agiscono sui Widget. E' però possibile simulare la funzionalità utilizzando (un po' impropriamente) il meccanismo dei CustomSecurityConstraint, che permettono di definire la logica con cui una singola voce di menu deve essere abilitata/visibile.


Q: Ho aggiunto alcuni widget nella mia schermata (nella quale sono anche modellati degli ScreenState) ma essi non risultano visibili dopo la rigenerazione. Come mai?

A: Se in una schermata (ContnetPanel) sono modellati uno o più ScreenState, quando si aggiunge un Widget occorre ricordarsi di gestire anche il nuovo widget nei vari ScreenState, altrimenti non sarà mai abilitato/visibile


Q: Ho la necessità di implementare il pattern di popolamento di combo a cascata: come posso fare?

A: In guigen non esiste il concetto di dipendenza modellata tra combo, ma è possibile implementare il pattern di interazione richiesto sfruttando altri strumenti:

Nel dettaglio esistono poi differenti modalità con cui può essere declinata questo suggerimento implementativo, che si dividono in due classi principali:

  1. la logica di caricamento della seconda combo è modellata direttamente nella catena di comandi associata all'EventHandler che agisce sulla prima combo
  2. la logica di caricamento della seconda combo è modellata come comando eseguito all' onRefresh della schermata, mentre sulla prima combo viene solo modellato l'event-handler necessario per far scatenare il refresh (e quindi la logica)

combo dipendenti con logica sull'evento della prima combo

Sulla combo A viene modellata una struttura simile alla seguente:

     	- ComboBox A
     	-- EventHandler(VALUE_CHANGED)
     	--- ExecCommand(caricaSecondaCombo)
     	---- CommandOutcome:OK
     	----- NOPCommand (o RefreshViewCommand)
     	

nel codice del metodo caricaSecondaCombo viene scritta la logica di valorizzazione dell'ApplicationData associato alla combo B. Per fare ciò si ha a disposizione il valore selezionato nella combo A: è in questo modo che si crea la dipendenza tra la combo A e la combo B.

combo dipendenti con logica sull'onRefresh della schermata

Sulla combo A viene modellata una struttura simile alla seguente:

     	- ComboBox A
     	-- EventHandler(VALUE_CHANGED)
     	--- NOPCommand (o RefreshViewCommand)
     	

sul comando onRefresh della schermata viene modellata una struttura simile alla seguente:

     	- ExecCommand(gestisciComboB)
     	-- CommandOutcome:OK
     	--- NOPCommand
     	

nel codice del metodo gestisciCombo viene scritta la logica di valorizzazione dell'ApplicationData associato alla combo B. Per fare ciò si ha a disposizione il valore selezionato nella combo A: è in questo modo che si crea la dipendenza tra la combo A e la combo B. La valorizzazione deve essere effettuata con il seguente schema algoritmico (pseudo-linguaggio):

     	se (comboA.valore == null) allora {
     	  comboB.dati = {}
     	}
     	altrimenti {
     	  comboB.dati = calcolaDatiB(comboA.valore);
     	}
     	
In questo modo si gestisce in qualche modo una "invariante" che lega il valore della combo A e i dati della combo B.

Nota: la seconda soluzione è preferibile in molti casi perchè gestisce correttamente la valorizzazione della combo indipendentemente dal flusso tra le schermate.


Q: Ho la necessità di visualizzare una pagina/report pdf in una nuova finestra o in un popup dialog. Come posso fare?

A: Attualmente non essite una funzionalità nativa per aprire una nuova finestra. E' possibile, sotto certe condizioni, utilizzare le funzionalità attualmente disponibili. La soluzione proposta è la seguente:

  1. Modellare uno UserDefinedWidget che conterrà i link alle pagine da aprire in una nuova finestra (il link può essere anche il puntamento ad una custom action che effettua il rendering di un PDF)
  2. Nello UserDefinedWidget inserire il codice html del link. Poichè occorrerà successivamente referenziare univocamente nel DOM il nodo corrispondente, è opportuno associare una classe css apposita o un ID specifico.
  3. Nella regione protetta del file javascript di arricchimento della schermata che contiene lo UserDefinedWidget inserire il codice di arricchimento che fa in modo di aprire il link in una popup/nuova finestra (vedi esempio sotto).
Condizioni di utilizzabilità di questa soluzione: Di seguito un esempio di codice html da inserire nello UserDefinedWidget:
		<!--PROTECTED REGION ID(R1304109634) ENABLED START-->
		<!-- Inserire qui il codice del widget -->
		<a href="http://www.csi.it" class="stdmdd720_newwin">clicca qui per aprire in una nuova finestra</a>
		<a href="http://www.csi.it" class="stdmdd720_dialog">clicca qui per aprire in una popup</a>
		<!--PROTECTED REGION END-->
     
Quindi un esempio di codice javascript che realizza il custom enrichment:
     
/**
 * Arricchimenti custom
 */
function initCustomEnrichments4CpTest(){
/*PROTECTED REGION ID(R261996033) ENABLED START*/

	// apertura in NUOVA FINESTRA 
	var myLinkNewWin = Ext.query(".stdmdd720_newwin"); 
		
	Ext.each(myLinkNewWin, function(elemento, index){

		elemento.onclick = function(){			 
			var dialog = new ExtCsi.ui.windows.HtmlPopup({url: elemento.href, height: '400', width: '600', resizable: true, scrollable: true, toolbar: true});
			dialog.show();
			return false;
		};

	});

	// apertura in POPUP DIALOG
	var myLinkDialog = Ext.query(".stdmdd720_dialog"); 
	
	Ext.each(myLinkDialog, function(elemento, index){

		elemento.onclick = function(){
			var dialog = new  ExtCsi.ui.windows.HtmlDialog({url: elemento.href, height: '400', width: '600', resizable: true});
			dialog.show();
			return false;
		};
	});
/*PROTECTED REGION END*/
}