In questo paragrafo vediamo le dinamiche del pannello, cioè come controllare il suo comportamento a runtime tramite eventi e metodi. Occorre tenere conto, tuttavia, che spesso si farà riferimento alla datamap principale del pannello che ne coordina l’accesso ai dati.
Fase di ricerca e visualizzazione dei dati #
Quando un pannello viene aperto, è possibile gestire automaticamente o manualmente il caricamento iniziale dei dati. A tal fine è possibile impostare a design time la proprietà status del pannello a qbe invece che data per evitare il caricamento iniziale.
Il valore di design time della proprietà status viene usato per allineare la proprietà autoload della datamap principale. Nel caso in cui i dati non siano stati caricati, è possibile farlo chiamando il metodo load sulla datamap principale. In questo modo, se si vogliono impostare filtri di default sulla datamap è possibile usare il metodo addFilter prima di chiamare load.
Occorre tenere presente che il pannello manipola i filtri della datamap in funzione dei filtri inseriti dall’utente. Se quindi l’utente cancella tutti i filtri, verranno eliminati anche quelli. Se pertanto si desidera aggiungere filtri permanenti, si consideri di impostarli nell’evento beforeLoad della datamap principale.
In funzione della proprietà searchMode, i campi di ricerca saranno visibili o meno. Se sono sempre visibili, il pannello potrà eseguire ricerche multiple senza cambiare stato. In ogni caso, è possibile investigare il valore inserito dall’utente per la ricerca tramite il metodo getFilter della datamap.
Configurazione della visualizzazione delle righe #
In molti casi è necessario modificare lo stile o altre proprietà della singola riga della cella in funzione del contenuto della stessa. A tal fine è possibile utilizzare l’evento onRowComposition del pannello. Esso viene notificato quando è necessario aggiornare la visualizzazione delle righe del pannello e, come l’analogo evento delle datamap, permette di configurare lo stato degli elementi della riga per cui viene chiamato.
Se, ad esempio, volessimo evidenziare in rosso i nomi dei prodotti sotto scorta, potremmo utilizzare il seguente codice:
$idfPanel.onRowComposition = function(event)
{
event.template.ProductName.style.color =
(event.row.UnitsInStock < event.row.ReorderLevel) ? "red" : "";
};
Se si volesse evidenziare in rosso tutti i campi della riga si potrebbe usare il seguente codice:
let c = (event.row.UnitsInStock < event.row.ReorderLevel) ? "red" : "";
for (let fn in event.template) {
let f = event.template[fn];
f.style.color = c;
}
Le proprietà configurabili degli elementi del template passato tramite il parametro event sono le seguenti:
- text: quello che viene visualizzato nella cella.
- enabled: indica se la cella è abilitata alla modifica o meno (in caso di pannello sbloccato).
- visible: indica se la cella è visibile o meno.
- style: stile CSS della cella e del suo contenuto.
- alignment: allineamento della cella. Usare i valori della lista App.IdfField.alignments.
- badge: testo che appare come badge della cella.
- mask: maschera di inserimento e visualizzazione della cella.
- errorText: testo di errore della cella.
Accesso ai dati della riga attiva #
Se si desidera accedere ai dati della riga attiva del pannello è possibile farlo in diverse modalità a seconda del momento in cui si vuole fare questa operazione.
Se si vuole accedere ai dati della riga attiva mentre essa cambia occorre utilizzare l’evento onRowChanged che viene notificato sia al cambio riga, sia se i dati della riga attiva cambiano, ad esempio perché l’utente li sta modificando. Nei parametri passati dall’evento abbiamo sia la nuova riga che la precedente. Il seguente codice modifica il titolo del pannello in funzione del nome del prodotto presente sulla riga attiva.
$idfPanel.onRowChanged = function(event)
{
this.caption = event.newRow.ProductName;
};
Si ricorda che se il pannello è basato su documenti, è possibile accedere al documento presente sulla riga attiva tramite la proprietà document della riga; nel caso precedente sarebbe stato event.newRow.document.
Se si vuole accedere ai dati della riga attiva in modo statico, cioè fuori da contesti in cui la riga attiva cambia, è possibile accedere direttamente alla riga attiva della datamap principale, ad esempio con il codice seguente:
$button.onClick = function(event)
{
app.alert($mainDM.row.ProductName);
};
Si tenga presente che la datamap potrebbe non avere una riga attiva; in tal caso $mainDM.row vale null.
Infine se si accede alla riga attiva da elementi interni al pannello, è possibile usare anche la proprietà this.row, che viene impostata sugli elementi della riga del pannello stesso. Per leggere i dati al momento dell’attivazione di un campo di pannello, è quindi possibile usare il seguente codice:
$QuantityPerUnit.onActivated = function(event)
{
app.alert(this.row.ProductName);
};
Selezione multipla #
I pannelli supportano anche la selezione multipla dei dati. Tale opzione è attivabile sia tramite i comandi di interfaccia utente che da codice.
Per consentire all’utente di attivare la selezione multipla, è necessario impostare a true la proprietà enableMultipleSelection del pannello. Per attivarla da codice è necessario impostare a true la proprietà showMultipleSelection.
Le righe attualmente selezionate nel pannello sono controllabili tramite interfaccia utente o da codice. In questo secondo caso, è possibile usare i metodi di pannello changeSelection per cambiare la selezione di tutte le righe, oppure setRowSelected per modificare lo stato di selezione di una singola riga.
Se, ad esempio, si desidera attivare o disattivare la selezione di una riga cliccando su un campo della stessa, è possibile utilizzare il seguente codice:
$ProductName.onActivated = function(event)
{
let sel = $idfPanel.isRowSelected($mainDM.row);
yield $idfPanel.setRowSelected($mainDM.row, !sel);
};
Per permettere di personalizzare la modifica della selezione delle righe, il pannello notifica l’evento onChangeSelection tutte le volte che una riga cambia stato di selezione oppure se è stato usato un comando di cambiamento complessivo.
Si noti infine che alcuni comandi di pannello, come la cancellazione o l’esportazione, agiscono sulle righe selezionate se la selezione multipla è mostrata a video.
Query di decodifica e lookup #
Molto spesso i pannelli devono mostrare dati di tabelle che contengono referenze ad altre tabelle. Ad esempio, in una tabella degli ordini sarà presente il campo ID Cliente, che rappresenta la referenza al cliente che ha effettuato l’ordine.
In questi casi generalmente non si desidera mostrare il dato così com’è, ma si preferisce effettuare una decodifica e portare a video una o più proprietà dell’oggetto referenziato. Nell’esempio precedente si potrebbe mostrare la denominazione del cliente che ha effettuato l’ordine.
Anche nella fase di modifica dei dati, per selezionare i dati desiderati non è possibile inserire direttamente il valore dell’ID Cliente, ma occorre reperirlo tramite un’operazione di lookup sulla tabella dei clienti.
Per poter organizzare queste operazioni, è possibile aggiungere una datamap all’interno di un campo del pannello. Impostando la proprietà controlType del campo al valore combo, si otterrà un controllo di tipo autocomplete che utilizza la datamap interna come sorgente di valori.
i campi SupplierID e CategoryID hanno una datamap interna per lookup e decodifica
In questa configurazione, il pannello utilizza le datamap per operazioni sia di decodifica che di lookup. In particolare, quando i dati devono essere mostrati a video, la datamap estrae una sola riga per esprimere la decodifica. Quando l’utente vuole modificare il valore del campo tramite lookup, il pannello usa la datamap interna togliendo il vincolo sul campo in fase di editing per estrarre la lista dei possibili valori e permettere all’utente di sceglierli.
Le datamap di lookup possono essere basate sia su query che su documenti. In entrambi i casi devono estrarre due colonne, la prima deve contenere il valore da inserire nel campo mentre la seconda la decodifica. Nell’immagine precedente viene mostrata la configurazione delle proprietà di queste datamap.
Se si desidera modificare le proprietà selezionate in una datamap di lookup creata automaticamente dal pannello, è possibile modificare la proprietà Derivato da della datamap indicando quale proprietà del documento deve essere collegata.
Nel caso si voglia utilizzare una concatenazione di campi è possibile utilizzare una proprietà unbound valorizzata nell’evento afterLoad del documento e poi utilizzare quella come proprietà selezionata dalla datamap di lookup.
Se la datamap di lookup è basata su query, occorre specificare il legame con il campo di pannello che la contiene referenziandolo tramite this.parentElement.value, come mostrato nell’immagine seguente.
Datamap di lookup basata su query
Le datamap di lookup presentano alcune proprietà che ne configurano il funzionamento. In particolare:
- LookupCacheMode: questa proprietà permette alla datamap di memorizzare i valori ottenuti in fase di decodifica per poterli riutilizzare risparmiando ulteriori query. I possibili valore sono:
- none: nessuna operazione di cache viene effettuata,
- single: viene memorizzato e riutilizzato il valore restituito della query di decodifica di ogni riga. È nella maggior parte dei casi il valore migliore perché permette di risparmiare query senza sovraccaricare la memoria del pannello.
- full: l’intera lista di possibili valori viene caricata una sola volta all’apertura del pannello e nessuna query viene più effettuata per le operazioni di decodifica. Questo valore è il migliore se il numero di righe della tabella da decodificare è basso, nell’ordine delle decine di righe.
- EnableValidation: se questa proprietà è true, in fase di modifica è necessario che l’utente selezioni un valore. Non è ammesso il valore null per questo campo.
È possibile personalizzare il comportamento delle datamap di lookup e decodifica come per ogni altra datamap, intercettandone gli eventi, in particolare beforeLoad e onSearch. Infine si ricorda che se si utilizza una datamap di lookup con cache dei dati, è possibile resettare il contenuto della cache usando il metodo refreshLookup del campo che contiene la datamap di lookup.
Fase di modifica e salvataggio dei dati #
I pannelli ammettono le operazioni di modifica, inserimento e cancellazione sia nella modalità lista che dettaglio. Tali operazioni vengono normalmente pilotate tramite i comandi della toolbar nel seguente modo.
Per default, quando appaiono i dati il pannello è bloccato. Si può modificare questa impostazione disattivando la proprietà locked del pannello. Tuttavia in questo caso è possibile che l’utente modifichi i dati senza volerlo.
Le operazioni di modifica e cancellazione avvengono nel layout attuale dopo aver sbloccato il pannello tramite il relativo comando della toolbar. L’inserimento può avvenire anche a pannello bloccato, se la proprietà del pannello enableInsertWhenLocked è attiva. Se automaticLayout è true, cliccando il pulsante di inserimento il pannello viene portato in dettaglio.
È possibile personalizzare questi passaggi intercettando gli eventi onLockingChanging e onStatusChanged del pannello.
Se si desidera personalizzare la gestione delle modifiche ai dati, occorre invece utilizzare gli eventi della datamap o del documento, se la datamap è basata su documenti. Si rimanda alla documentazione relativa per maggiori informazioni.
Modifica programmatica dei dati #
Durante la fase di modifica dei dati è possibile modificare i dati anche in maniera programmatica, sempre utilizzando l’accesso alla riga attiva del pannello tramite la proprietà row della datamap principale.
Vediamo come esempio l’utilizzo di una videata di lookup esterna per recuperare un’informazione necessaria nel pannello attuale. Come esempio di partenza utilizziamo un pannello sulla tabella RigheOrdine che necessita di una videata di lookup sulla tabella Ordini per scegliere il numero d’ordine.
Attivando il campo OrderID del pannello RigheOrdine apriamo la videata di lookup, con il seguente codice:
$OrderID.onActivated = function(event)
{
App.Pages.push(app, App.Ordini, {popup : true, modal : true});
};
Dopo aver scelto l’ordine giusto, chiudiamo la videata di lookup con il seguente codice:
$idfPanel.onDblclick = function(event)
{
App.Pages.pop(app, 1, {row : $mainDM.row});
};
e infine dalla videata RigheOrdini applichiamo alla riga corrente l’ID ordine selezionato con la videata di lookup intercettando l’evento onBack.
App.RigheOrdine.prototype.onBack = function(sender, info)
{
if (info.row && info.row.OrderID) {
$mainDM.row.OrderID = info.row.OrderID;
}
};
Caricamento e visualizzazione di documenti (blob) #
I pannelli mettono a disposizione la funzionalità di caricamento, visualizzazione e scaricamento di documenti (file) in modo completamente automatico. Sarà sufficiente aggiungere un campo di tipo blob al database e configurare le opzioni del campo di pannello corrispondente. In particolare:
- maxUploadSize: dimensione massima caricabile in byte. Default 10 MB.
- maxAutoShowSize: dimensione massima visualizzabile al volo nel pannello. Default 50 KB. Se il contenuto del blob è maggiore di questa dimensione, verrà mostrato un link per la visualizzazione in una nuova pagina.
Il blob può essere visualizzato e modificato sia in lista che in dettaglio. Posizionando il cursore su un campo blob apparirà una toolbar che ne permette la gestione. La toolbar contiene i seguenti pulsanti:
- Carica: permette di caricare un nuovo file nel campo. Disponibile solo se il campo non è di sola lettura.
- Cancella: Svuota il contenuto del campo, eliminando il file. Disponibile solo se il campo non è di sola lettura.
- Apri in nuova pagina: mostra il contenuto del campo in una nuova pagina browser.
I file vengono caricati nella datamap in formato ArrayBuffer e poi vengono salvati nel database nel campo blob collegato. È quindi possibile interagire tramite l’evento onUpload del campo di pannello per manipolare il contenuto del blob in modo diverso, ad esempio salvando il blob sul file system invece che direttamente nel database.
Si segnala infine che i campi blob mostrati in anteprima nel pannello o in una nuova pagina vengono salvati come file temporanei nel file system del server con un nome casuale per poter essere visualizzati dal browser. Al termine della sessione i file temporanei vengono rimossi dal sistema.
Esempio di campo blob in un pannello
Uso di campi statici con funzione multi-upload #
I pannelli permettono di realizzare facilmente superfici di caricamento di file non direttamente collegati al database. A tal fine è necessario aggiungere un nuovo campo al pannello non collegato ad alcuna proprietà della datamap e non mostrato in lista, cioè con il flag ShowInList non selezionato. A questo punto è possibile attivare il flag MultiUpload nelle proprietà del campo ed impostare eventualmente le proprietà MaxUploadSize, MaxUploadFiles e UploadExtensions per limitare numero, dimensione e tipo di file che sarà possibile caricare.
A runtime, il caricamento avviene cliccando sul campo oppure trascinando i file dal computer nel campo. Lato server viene notificato l’evento onUpload che contiene i file caricati. Essi dovranno essere gestiti o cancellati dal file system. Se non si esegue nessuna operazione i file rimarranno nel file system del server per sempre.
Si noti che il campo statico gestisce solo il caricamento dei file, quindi non ha alcun cambiamento grafico durante e dopo questa operazione. È tuttavia possibile utilizzare la proprietà innerHtml del campo per inserire testi o altri elementi nel campo.
Coordinamento di più pannelli #
All’interno dell’interfaccia utente della propria applicazione è possibile coordinare il contenuto di più pannelli. Questo avviene normalmente assegnando la proprietà document o collection della datamap principale dei pannelli collegati.
Master detail #
Vediamo ora una videata che contiene la lista degli ordini e, per ognuno di essi, l’elenco delle righe corrispondenti.
Il coordinamento fra i due pannelli viene gestito dall’evento onRowChanged del pannello di testata, tramite il seguente codice:
$pannelloTestata.onRowChanged = function(event)
{
let o = event.newRow.document; // type:NwindLibreria.Orders
yield o.OrderDetails.load();
$pannelloDettaglio.mainDM.collection = o.OrderDetails;
};
Si noti che in questo caso è necessario caricare la collection delle righe perché quando il pannello di testata carica i dati esegue una query unica sulla tabella degli ordini e quindi le righe di ognuno degli ordini non sono caricate subito.
Esempio di videata master-detail
List detail #
In questo secondo esempio vogliamo visualizzare nella parte sinistra della videata la lista dei prodotti, mentre nella parte destra tutti i dettagli del prodotto selezionato nella lista.
Esempio di videata list-detail
Anche in questo caso il coordinamento fra i due pannelli viene gestito dall’evento onRowChanged del pannello di lista, tramite il seguente codice:
$listPanel.onRowChanged = function(event)
{
$detPanel.mainDM.document = event.newRow ? event.newRow.document : null;
};
In questo caso il documento attivo nel pannello di lista viene direttamente passato alla datamap principale del pannello di dettaglio impostando la proprietà document.
Coordinamento fra più di due pannelli #
Utilizzando una combinazione delle tecniche precedenti è possibile coordinare anche più di due pannelli, anche se contenuti in videate diverse.
È importante notare che condividendo i documenti caricati in memoria fra più datamap si ottiene la sincronizzazione visuale delle variazioni senza dover effettuare query di refresh dei dati. È quindi il metodo di coordinamento da preferire.