vai al contenuto principale

Tipi di Handler e modellazione

Tipi di Handler #

Come accennato in Ciclo di Sviluppo, gli Handler Livebase si dividono in due gruppi: i modellabili (o model-dependent), che dipendono dalle classi e da altri elementi definiti in un modello, e i default, che invece esistono a prescindere dal modello usato.

Handler

Gli Handler modellabili sono a loro volta divisi in due famiglie: Persistency Handlers e GUI Handlers:

  • Persistency Handlers: include il SaveActionHandler, il ValidationHandler e i tre DatabaseHandler (DatabaseInsert, DatabaseUpdate, DatabaseDelete). Consentono di gestire le diverse fasi del processo di salvataggio/modifica di un oggetto e vengono richiamati nell’ordine in cui li abbiamo elencati. Il SaveActionHandler consente di gestire i campi di una form in scrittura, il ValidationHandler di eseguire una validazione custom dell’istanza di una classe, infine i DatabaseHandler si collocano nella fase di commit della transazione sul database.
  • GUI Handlers: include il FormActionHandler, il ListActionHandler, l’ObjectPrintHandler e il ListPrintHandler. Vengono richiamati a partire da eventi custom definiti in uno o più Application Schema; generalmente questi eventi consistono nel click di un utente su un bottone apposito per l’esecuzione del plugin: il FormAction e l’ObjectPrint si collocano sul gestore d’istanza (la form, appunto), mentre il ListAction e il ListPrint si collocano sul gestore di classe (la Livetable o Editable). Gli ActionHandler sono general-purpose, mentre i PrintHandler offrono supporto per esportare il contenuto della form/tabella in un formato opportuno per la stampa.

Gli Handler di default sono l’AuthenticationHandler, il LogoutHandler e lo ScheduledTask).

Modellare gli Handler #

Per aggiungere un Handler a una classe del modello occorre, dal Database Schema , aprire il Class menu (facendo click destro sull’header della classe) e selezionare la voce New handler corrispondente tra quelle disponibili, come mostrato in figura:

Des classMenu handlers

Una volta scelto il tipo di Handler dovremo:

  • Digitare un nome, che dovrà essere univoco nel namespace della classe.
    Una buona pratica è usare un nome rappresentativo dell’azione che vogliamo far compiere, ad esempio sendEmail se l’Handler invierà una mail.
  • Per alcuni Handler (tra cui il FormActionHandler) è necessaria una configurazione ulteriore nell’Application Schema .
  • Per alcuni Handler sono inoltre disponibili opzioni extra nel menù contestuale Service Handler menu, accessibile facendo click destro sull’Handler dal Database Schema . [LINK]

Handler e spazio dei nomi #

Gli Handler definiti su una classe condividono un namespace separato da quello degli attributi. Nel diagramma sono elencati in un’altra lista sotto quella degli attributi; ad esempio, in figura sono presenti tre diversi Handler su Class1.

Des handlers example

Handler e viste applicative #

Analogamente agli altri elementi del diagramma – classi, attributi, relazioni – è possibile abilitare/disabilitare un Handler per una specifica vista applicativa: per farlo è sufficiente cliccare sull’Handler dall’Application Schema per fare toggle del suo stato, così come illustrato nel tutorial (Definisci viste applicative e profili utente). Ciò consente di scegliere in quali viste applicative consentire l’uso di un certo plugin su quella classe.

Des handlers example app schema

Persistency Handler #

SaveActionHandler #

Viene richiamato ogniqualvolta nell’applicazione viene confermato l’inserimento di dati e si verifica quindi l’evento save; gli scenari attuali sono due:

  • In una form, click sul pulsante Save (o Confirm nel caso di oggetto part) dopo aver compilato i campi per un oggetto.
  • Click del pulsante Save di una Editable, dopo averne modificato il contenuto aggiungendo/modificando/eliminando righe.

La action verrà eseguita prima che i dati vengano validati e salvati sul database; questo consente di gestire in scrittura i campi compilati dall’utente ed eventualmente modificarne il contenuto – ad esempio, formattare gli attributi nativi o aggiungere informazioni sul Database e collegarle all’oggetto creato inizializzando relazioni di default.

Lo sviluppatore può specificare le operazioni da far eseguire all’handler implementando il metodo doAction() della relativa classe, e interagendo con il contesto (SaveActionHandlerContext) che ne costituisce il parametro.

SaveActionHandlerContext #

L’entity può essere acceduta sia nella versione precedente alla modifica che in quella attuale, ed in quest’ultimo caso possono essere scritti sia i campi interni dell’entity che quelli di eventuali oggetti correlati.

L’handler può unicamente restituire un risultato vuoto tramite la chiamata none(), effettuata sull’oggetto result offerto dal contesto.

ValidationHandler #

Viene richiamato dopo il SaveActionHandler (se presente sulla stessa classe) e prima del commit di una transazione sul Database relativa a una delle operazioni (insert o update) sulle istanze di una specifica classe. Questo Handler consente di implementare una logica di validazione personalizzata dell’istanza della classe, con la quale poter controllare regole non esprimibili nel modello (ad esempio, tramite restrizioni sul dominio degli attributi o Class warning, oppure regole la cui specificità rende l’approccio low-code troppo oneroso – ad esempio, eseguire una chiamata a un servizio esterno per validare dei campi.

Lo sviluppatore definisce il processo di validazione implementando il metodo validate() della classe dell’handler, e interagendo con il contesto (ValidationHandlerContext) che ne costituisce il parametro.

ValidationHandlerContext #

L’entity coinvolta può essere acceduta nella versione precedente e in quella attuale, ma unicamente in lettura.

L’handler deve restituire un risultato che indichi il successo o il fallimento della validazione personalizzata. Per fare questo, una volta ottenuto il result, si deve effettuare una delle seguenti chiamate:

  • success() : Per segnalare l’esito positivo;
  • failure(msg) : Per segnalare l’esito negativo. A questa chiamata va passata una stringa che rappresenta un messaggio di errore, il quale verrà mostrato in una finestra di dialogo durante l’esecuzione dell’applicazione.

È offerto anche l’accesso all’insieme dei valori localizzati per la Cloudlet, come sono stati definiti all’interno del Designer, con l’invocazione del metodo getCloudletLocales().

ValidationHandler bloccanti #

Di default, la validazione aggiuntiva eseguita da un ValidationHandler è non bloccante: se l’azione di save non viene validata con successo, l’applicazione mostra un warning consentendo all’utente di confermare o meno, in modo del tutto analogo a quanto accade per i Class warning non bloccanti.

Possiamo modificare questo comportamento agendo sul flag Blocking dal menù contestuale accessibile facendo click destro sul ValidationHandler: spuntando questa opzione la validazione sarà bloccante, e in caso di validazione negativa il framework rifiuterà la save.

Il metodo failure(msg) del context ha un parametro booleano opzionale, che permette di sovrascrivere in determinati casi il comportamento specificato nel Designer: un valore di true renderà la validazione bloccante, mentre un valore di false la renderà non bloccante.

DatabaseHandler #

Per ogni transazione esiste una variante specifica di questo Handler: DatabaseInsertHandler, DatabaseUpdateHandler e DatabaseDeleteHandler. Ciascuno viene richiamato prima e dopo il commit di una transazione sul Database relativa a una delle operazioni (insert, update, delete) sulle istanze di una specifica classe. Nel caso delle prime due, ciò avviene sempre dopo il ValidationHandler (se è presente sulla stessa classe). Questi Handler consentono di eseguire azioni che dipendono dal contenuto di una transazione – ad esempio, eseguire una query SQL custom.

È possibile cambiare il tipo di transazione che richiama il DatabaseHandler dalla voce Change type dal menù contestuale dell’Handler.

Ciascun DatabaseHandler richiede l’implementazione di due metodi da parte dello sviluppatore: beforeCommit(), per specificare le operazioni da eseguire prima del commit della transazione, e afterCommit(), per le operazioni successive allo stesso commit. Nel primo caso, il contesto offerto è un TransactionalContext, mentre nel secondo caso un Context.

Contesti di esecuzione #

Sono consentiti accessi alla Entity unicamente in lettura, ed in funzione del fatto che si stia operando prima o dopo il commit, si può avere visibilità della versione attuale (new) e/o precedente (old):

Tipo handlerMetodo implementatoAccessi
InsertbeforeCommitnew
InsertafterCommitnew
UpdatebeforeCommitnew/old
UpdateafterCommitnew
DeletebeforeCommitold
DeleteafterCommitNO

L’unico risultato restituibile dai metodi di un DatabaseHandler è una conclusione positiva, ottenibile invocando sul result i metodi none() o reloadObjectData(). Il primo non esegue ulteriori operazioni, mentre il secondo forza l’aggiornamento della form dell’oggetto manipolato, senza dover cliccare il pulsante Aggiorna all’interno dell’applicazione.

DatabaseHandler bloccanti #

Di default, i DatabaseHandler non sollevano eccezioni quando non sono implementati. Dal Service handler menu è possibile spuntare l’opzione Implementation required e forzare l’applicazione a richiedere l’esecuzione di un Plugin che implementi l’Handler corrispondente, bloccando il persist sul DB se il Plugin non è presente nella Cloudlet o se è inattivo.

Des service handler menu handlers

GUI Handler #

FormActionHandler #

Viene richiamato quando si verifica un evento personalizzato nella form di un oggetto. Dopo averlo creato, è necessario quindi definire anche l’evento nel Form Layout Editor , nella vista applicativa per cui l’Handler è abilitato. I modi per definire l’evento sono due:

  • Click sul bottone Exec plugin. Per aggiungerlo, trascinalo nella form dalla tab Buttons, seleziona l’Handler da invocare e clicca su OK.
  • Evento onChange lanciato da un widget della form. Per impostarlo, fai click destro sul widget desiderato → Set events → onChange e seleziona l’Handler.

Des form layout editor handler button

Un bottone Exec plugin può essere personalizzato ulteriormente selezionandolo ed editando le sue properties. Da type possiamo scegliere di nuovo quale Handler richiamare, mentre da enabledIf possiamo scegliere se mostrarlo esclusivamente in una delle due modalità della form (edit o view), oppure in entrambe.

Des form layout editor handler button properties

Il bottone comparirà nell’applicazione generata insieme agli altri campi della form.

App form formAction handlerButton

A differenza dei Persistency Handlers, che si attivano in una sequenza ben precisa, i FormActionHandler possono eseguire la propria sequenza di azioni in ogni momento. Queste azioni vanno specificate implementando il metodo doAction() della classe relativa e interagendo con il FormActionContext che ne costituisce il parametro.

FormActionContext #

L’Entity a cui fa riferimento la form è accessibile nella sola versione in corso di modifica, sia in lettura che in scrittura. É inoltre possibile accedere alla sessione con il database, nella sua versione mutabile, con il metodo getEntitySession() offerto dal contesto.

Ulteriori funzionalità offerte dal contesto sono:

  • La possibilità di ottenere informazioni sull’evento che ha attivato l’handler, tramite il metodo getEventSource(). Queste informazioni includono la tipologia di evento (clic sul pulsante o onChange) e il campo della form a cui esso è collegato;
  • L’accesso al file storage della Cloudlet, tramite il metodo getFileService();
  • L’accesso a un JSON di parametri passato da un client, tramite il metodo getHandlerCustomParams().

I risultati restituibili da un FormActionHandler sono:

  • none() : Un risultato positivo che non esegue ulteriori azioni;
  • download(file, fileName) : Ordina il download di un file. Opzionalmente è possibile passare anche il nome con il quale salvare quel file sul filesystem di destinazione;
  • preview(file, fileName) : Apre una nuova scheda nel browser di esecuzione della Cloudlet, contenente l’anteprima di un file. Da questa scheda si potrà decidere se scaricare o meno il file, ed anche in questo caso si prevede il parametro opzionale per specificare il nome con il quale salvarlo;
  • withMessage(msg) : Mostra una finestra di dialogo contenente il messaggio passato come parametro. A questa chiamata ne deve seguire una ulteriore che specifica il livello di severità del messaggio:
    • info() : Semplice messaggio informativo, senza valenza di errore;
    • warning() : Messaggio di avvertimento o di errore generalmente non bloccante;
    • error() : Messaggio di errore bloccante.
  • withPayload(json) : Restituisce un JSON, specificato come stringa e passato come parametro, a un client che stia accedendo alla Cloudlet.

FormActionHandler e Form class #

Il Service handler menu del FormActionHandler offre il comando Set form..., che consente di agganciare una Form class alla classe sui cui è definito l’Handler.

Des handlers serviceHandlerMenu setFormClass

Nell’applicazione, il bottone useMyForm farà comparire la form myForm in popup dalla posizione desiderata. In questo modo l’Handler dentro Class1 avrà a disposizione anche il contesto di myForm (tutti i campi compilati con i rispettivi valori): questo si ottiene chiamando getHandlerParams() sul contesto del FormActionHandler.

Una Form Class, al pari delle normali classi, può avere dei FormActionHandler collegabili ai suoi pulsanti o a eventi onChange(), in questo caso è possibile accedere ai campi compilati tramite il metodo getEntity() e i possibili risultati sono gli stessi di un handler definito sulle altre classi non-Form.

ListActionHandler #

È analogo al FormActionHandler, ma viene richiamato cliccando su un bottone sulla Livetable di una classe.

Questo consente di eseguire azioni in blocco sugli oggetti della classe – ad esempio modificare un sottoinsieme di oggetti selezionati oppure creare nuovi oggetti preconfigurati.

App lt listAction handlerButton

Possiamo configurarlo dal List layout editor della classe su cui è abilitato in una vista applicativa. Come abbiamo visto in Modella relazioni, l’editor è accessibile in due modi:

  • Configurando la Livetable/Editable dalla scheda Management del GUI widgets configurator della classe source (layout di default).
  • Editando le properties di un Association manager all’interno del Form layout editor di una classe associata (sovrascrittura del default per quello specifico ruolo).

Quando sono presenti Handler sulla classe, sull’editor viene mostrata l’area aggiuntiva Custom buttons. Per aggiungere un bottone, clicca in basso a destra sul pulsante New button..., seleziona l’Handler da eseguire e clicca su OK; questo comparirà nella colonna Custom buttons.

Des listLayoutEditor handlerButton

Facendo click destro sul bottone → Enable on possiamo scegliere la sua politica di attivazione. In questo modo possiamo mostrare il bottone dinamicamente solo quando un certo numero di oggetti è selezionato, e nasconderlo altrimenti.

Des listLayoutEditor handlerButton properties

Il bottone comparirà nell’applicazione generata sotto agli altri pulsanti della Livetable:

App lt listAction handlerButton

Per specificare le operazioni da far eseguire al ListActionHandler, è necessario implementare il metodo doAction() della rispettiva interfaccia, e interagire con il contesto ListActionContext che ne rappresenta il parametro.

ListActionContext #

Possiamo ottenere un Set delle Entity che abbiamo selezionato tramite l’applicazione, per mezzo del metodo getSelectedEntities(): tutte saranno accessibili sia in lettura che in scrittura, nella versione attuale. In più, il contesto dell’handler offre l’accesso alla sessione mutabile con il database (metodo getEntitySession()), per l’esecuzione di operazioni CRUD sul suo contenuto.

Come per un FormActionHandler sulla singola Entity, anche il ListActionHandler offre l’accesso al file storage della Cloudlet (metodo getFileService()), nonché al JSON con i parametri passati via client (metodo getHandlerCustomParams()). Una differenza rispetto al primo è però la possibilità di accedere ai valori localizzati, tramite il metodo getCloudletLocales()

Il metodo doAction() deve restituire uno dei seguenti risultati:

  • none() : Un risultato positivo che non esegue ulteriori azioni;
  • download(file) : Ordina il download di un file;
  • preview(file) : Apre una nuova scheda nel browser di esecuzione della Cloudlet, contenente l’anteprima di un file. Da questa scheda si potrà decidere se scaricare o meno il file;
  • withMessage(msg) : Mostra una finestra di dialogo contenente il messaggio passato come parametro. A questa chiamata ne deve seguire una ulteriore che specifica il livello di severità del messaggio:
    • info() : Semplice messaggio informativo, senza valenza di errore;
    • warning() : Messaggio di avvertimento o di errore generalmente non bloccante;
    • error() : Messaggio di errore bloccante.
  • showObjectDetail(id) : Apre la form di dettaglio dell’oggetto il cui identificativo è stato passato come parametro.

ListActionHandler e Form class #

Anche il menù contestuale del ListActionHandler offre il comando Set form..., che consente di agganciare una Form class alla classe sui cui è definito l’Handler.

ObjectPrintHandler #

Viene richiamato cliccando il pulsante Stampa di un oggetto di una specifica classe. Questo consente di esportare il contenuto della form per stamparlo secondo diversi formati.

Per ogni classe è possibile definire molteplici ObjectPrintHandler: in questo caso alla pressione del pulsante Stampa comparirà un menu che consentirà all’utente di scegliere il tipo di stampa desiderato.

App form print handler button

Le azioni da eseguire vanno specificate implementando il metodo print() della relativa interfaccia. È offerto l’accesso all’ObjectPrintContext che costituisce il parametro di questo metodo.

ObjectPrintContext #

È consentito accedere alla Entity in sola lettura, e nella sola versione attuale. Non è offerto l’accesso alla sessione con il database.

È offerto l’accesso ai valori localizzati definiti nella Cloudlet, tramite il metodo getCloudletLocales().

I risultati restituibili dall’Handler sono:

  • download(file, fileName): Ordina il download di un file. Opzionalmente è possibile passare anche il nome con il quale salvare quel file sul filesystem di destinazione. In alternativa al file si può passare il riferimento a uno stream o a un URI;
  • preview(file, fileName): Apre una nuova scheda nel browser di esecuzione della Cloudlet, contenente l’anteprima di un file. Da questa scheda si potrà decidere se scaricare o meno il file, ed anche in questo caso si prevede il parametro opzionale per specificare il nome con il quale salvarlo. In alternativa al file si può passare il riferimento a uno stream o a un URI;
  • withMessage(msg) : Mostra una finestra di dialogo contenente il messaggio passato come parametro. A questa chiamata ne deve seguire una ulteriore che specifica il livello di severità del messaggio:
    • info() : Semplice messaggio informativo, senza valenza di errore;
    • warning() : Messaggio di avvertimento o di errore generalmente non bloccante;
    • error() : Messaggio di errore bloccante.

ListPrintHandler #

Viene richiamato cliccando su un bottone di stampa sulla Livetable di una specifica classe. È analogo all’ObjectPrintHandler e consente di stampare le informazioni associate a un sottoinsieme di oggetti selezionati, oppure di tutti gli oggetti della tabella.

App lt print handler button

Le azioni da eseguire vanno specificate implementando il metodo print() della relativa interfaccia. È offerto l’accesso al ListPrintContext che costituisce il parametro di questo metodo.

ListPrintContext #

È possibile accedere alla sola sessione con il database, nella sua versione immutabile.

È possibile inoltre l’accesso ai valori localizzati definiti all’interno della Cloudlet, con il metodo getCloudletLocales() invocato sul contesto dell’handler.

Un ListPrintHandler ammette gli stessi risultati di un ObjectPrintHandler definito sul singolo oggetto.

Default Handlers #

AuthenticationHandler #

Viene richiamato al momento dell’autenticazione di un membro della Cloudlet, cioè quando l’utente inserisce username e password e clicca sul pulsante Login. Queste informazioni vengono passate all’AuthenticationHandler, che può eseguire un’autenticazione con regole personalizzate – ad esempio, eseguire controlli aggiuntivi sul Database oppure coordinarsi con un servizio esterno.

Lo sviluppatore deve implementare il metodo authenticate() dell’interfaccia SpiCloudletAuthenticationHandler, specificando le azioni da eseguire al momento del login. Lo stesso metodo offre un parametro di tipo AuthenticationData, che contiene le informazioni riguardanti la tipologia di autenticazione che è stata eseguita.

LogoutHandler #

Viene richiamato dopo un click sul pulsante Logout per eseguire un’azione personalizzata in questo punto dell’esecuzione – ad esempio, reindirizzare il browser a un’altra pagina (particolarmente utile se l’applicazione generata è collegata a un front-end personalizzato).

Lo sviluppatore deve implementare il metodo logout() dell’interfaccia SpiCloudletLogoutHandler, con la sequenza di azioni da eseguire al momento del logout. Il contesto che ne costituisce il parametro è un LogoutHandlerContext.

LogoutHandlerContext #

Offre i seguenti metodi di utilità:

  • getAuthenticationData() : Permette di ottenere la struttura AuthenticationData che è stata accennata nella sezione precedente, la quale contiene le informazioni relative all’autenticazione;
  • get__CloudletCurrentUser() : Permette di ottenere le informazioni sull’utente della Cloudlet che si è disconnesso.

Il LogoutHandler deve restituire un risultato che specifica se è necessario o meno un redirect a un altro URL. Nel primo caso, è necessario invocare il metodo none() sulla struttura ottenuta dalla chiamata a result(); nel secondo caso, va invocato il metodo sendRedirect(url) con la specifica dell’URL al quale reindirizzare.

ScheduledTask #

Gli ScheduledTask possono essere sfruttati per programmare routine indipendentemente dagli eventi che si verificano sull’applicazione; per ciascun task possiamo configurare la frequenza mediante un’espressione CRON (la cui sintassi è consultabile qui).

Lo sviluppatore deve implementare il metodo run() dell’interfaccia SpiCloudletScheduledTask, e decorare la classe appositamente definita con l’annotazione @CloudletScheduledTask. Nell’annotazione va specificata l’espressione CRON che ne definisce la frequenza di esecuzione. Il metodo run() equivale a una procedura, quindi non deve restituire alcun risultato.