In questo capitolo modificheremo l'app della lezione precedente per introdurre il
concetto di data binding, cioè il collegamento dei
dati tra l'interfaccia utente e la logica applicativa.
Creeremo una nuova finestra, contenente una TableView
collegata ad una
collezione Alloy. La nostra app ci consentirà di selezionare una riga dalla tabella,
in modo da utilizzare i dati in essa contenuti per valorizzare i campi di una schermata.
index.xml e index.tss
Modifichiamo prima di tutto la vista index.xml, per
aggiungere ad essa un nuovo pulsante elenco. Tramite quest'ultimo apriremo
la lista dei libri in una nuova finestra.
<Alloy>
<Window class="container" layout="vertical">
<TextField id="title" hintText="Titolo" top="40" class="text" />
<TextField id="author" hintText="Autore" top="10" class="text"/>
<TextField id="isbn" hintText="ISBN" top="10" class="text"/>
<Button id="save" top="10" title="salva" class="button" onClick="save"></Button>
<View top="10" height="Ti.UI.SIZE">
<Button id="prev" left="10" title="precedente" class="button" onClick="prev"></Button>
<Button id="next" right="10" title="successivo" class="button" onClick="next"></Button>
</View>
<Button id="list" top="10" title="elenco" class="button" onClick="list"></Button>
</Window>
</Alloy>
Anche il file dello stile, index.tss, cambia leggermente. Modificheremo infatti
la dimensione dei pulsanti affinchè occupino solo l'ampiezza
strettamente necessaria per essere disegnati (utilizzeremo la costante
Ti.UI.SIZE).
".button": {
height: "40",
width: Ti.UI.SIZE,
borderStyle: Ti.UI.INPUT_BORDERSTYLE_ROUNDED
}
Ci occorre ora aggiungere una nuova funzione di callback, che chiameremo list()
;
ma prima di scriverla creiamo un nuovo controller con lo stesso nome. Una
volta creatolo, apriamo il file list.xml e
modifichiamone il contenuto.
list.xml
<Alloy>
<Collection src="book" />
<Window class="container" backgroundColor="white" onClose="close" onClick="exit">
<TableView dataCollection="book" onClick="show" height="80%">
<TableViewRow title="{title}" alloy_id="{alloy_id}" />
</TableView>
</Window>
</Alloy>
Il tag Collection ha lo scopo di mostrare una
collezione book (quella che avevamo definito nel file app/models/book.js
nella lezione precedente), mentre l'attributo dataCollection
utilizzato nel tag TableView esprime il collegamento (data
binding) tra la collezione e la tabella. Da questo punto in
avanti, usando una notazione con le parentesi graffe, potremo far
riferimento ai singoli attributi di un modello tratto dalla
collezione book. La riga della tabella, infatti, avrà come titolo
il valore ricavato dall'attributo title del modello.
L'attributo alloy_id, invece, esprime una chiave univoca che
viene associata a ciascun modello all'atto del
salvataggio. Useremo questa chiave per poter selezionare un
modello della collezione senza dover scorrere gli elementi
come avveniva nel'app della lezione scorsa.
list.js
Il controller list.js conterrà le definizioni delle tre callback
show
, exit
e close
, e si occuperà
di passare il modello selezionato alla finestra in background,
utilizzando una callback passata come argomento da index.js.
Prima di tutto, il controller riceverà la callback come argomento,
e popolerà i valori della collezione dentro la TableView
attraverso il metodo fetch().
var args = arguments[0] || {};
var callback = args.onRowClickCallback;
Alloy.Collections.book.fetch();
La funzione exit()
servirà invece per chiudere la finestra una volta
selezionato un libro, che sarà arrivato anche
cliccando su un'area bianca al di fuori della tabella. Quando si
chiama il metodo close()
su un oggetto Ti.UI.Window,
quest'ultimo genererà un evento close
(gestibile con l'attributo onClose) che noi utilizzeremo per liberare la
memoria.
La funzione close()
sarà quindi la callback che verrà
eseguita alla chiusura della finestra. Il comando $.destroy
libererà la memoria occupata dalla collection e dal sistema di
data binding. È bene non dimenticare di eseguire questa operazione, per evitare che la memoria si esaurisca velocemente con l'utilizzo dell'app.
function exit()
{
// chiudo la finestra
$.list.close();
}
function close() {
// da chiamare per evitare memory leaks
$.destroy();
};
A questo punto si definisce la callback show()
, che verrà
associata al click su una riga della tabella, per selezionare
il libro di cui vogliamo ottenere i dati precedentemente
salvati.
function show(e) {
// mostro il valore di e nella console
Ti.API.info(JSON.stringify(e));
// mostra il modello attuale nella finestra sottostante
callback && callback(e.row.alloy_id);
// chiudi la finestra
exit();
};
La funzione show()
chiama la funzione di callback
passandole l'attributo alloy_id, che identifica univocamente
un modello all'interno di una collezione. La definizione
della funzione di callback è scritta nel
controller chiamante, ovvero index.js,
index.js
Le modifiche al sorgente di index.js sono prevalentemente
due: la callback list()
che apre la finestra con l'elenco
dei libri, e la callback che viene chiamata da list.js per
visualizzare il libro selezionato.
Analizziamo la chiamata alla callback list()
:
function list() {
var list = Alloy.createController('list', {
onRowClickCallback : showModelWithID
}).getView();
list.open();
}
In questa chiamata viene creato il controller list, e viene
passato come argomento un oggetto che contiene una coppia
chiave-valore, come il seguente:
{
onRowClickCallback : showModelWithID
}
onRowClickCallback è il nome dell'attributo che ha come valore il
riferimento alla funzione showModelWithID, che sarà quindi
la funzione di callback che verrà chiamata in list.js
quando selezioniamo un libro dalla tabella.
La definizione della funzione showModelWithID (e di quelle ad essa correlate) è la seguente:
function showModel(index) {
var model = books.at(index);
displayModel(model);
}
function showModelWithID(id) {
var model = books.get(id);
displayModel(model);
}
function displayModel(model) {
Ti.API.info(JSON.stringify(model));
$.title.value = model.get("title") || "";
$.author.value = model.get("author") || "";
$.isbn.value = model.get("isbn") || "";
}
In pratica la funzione showModel
della lezione precedente viene
utilizzata come base per la definizione di showModelWithID
. La
differenza è che mentre la prima estraeva un modello dalla
collezione usando un indice numerico, quest'ultima
lo estrae utilizzando la sua chiave univoca.
Entrambe, ottenuto il modello, chiameranno la funzione displayModel
per
visualizzare il libro selezionato.
Validare l'input
Nella scorsa lezione abbiamo accennato al fatto che è possibile
definire delle funzioni di validazione per il modello. Nell'app che stiamo costruendo,
immaginiamo che sia obbligatorio compilare i campi titolo ed
autore. La funzione di validazione si può definire estendendo il
modello, aggiungendo la callback associata all'attributo validate
nel file app/models/book.js.
extendModel : function(Model) {
_.extend(Model.prototype, {
// extended functions and properties go here
validate : function(attrs) {
var title = attrs["title"];
var author = attrs["author"];
if (title.length
Per richiamare il validatore appena definito, basta chiamare la funzione
isValid() sul
modello che vogliamo salvare. Nel caso della funzione save()
di index.js, la dovremo applicare sulla variabile book.
// Crea un modello di tipo 'book'
var book = Alloy.createModel('book', {
title : $.title.value,
author : $.author.value,
isbn : $.isbn.value
});
if (!book.isValid())
{
alert("titolo o autore mancanti");
return;
}
Il codice sorgente degli esempi utilizzati in questo articolo è allegato a questo articolo, nonchè
disponibile su GitHub.