Sencha Touch è un framework JavaScript che introduce un nuovo modo di sviluppare applicazioni Web per il mondo mobile. Tramite una sintassi ed una logica un po' particolari, ed in qualche modo ispirate al mondo Swing di Java, Sencha Touch ci consente di descrivere interfacce web attraverso istruzioni JavaScript e tecniche di templating.
Il framework si prende poi in carico l'onere di generare il documento HTML finale e nel farlo applica al descrittore di interfaccia un tema che può ricalcare quello delle applicazioni native di un particolare modello di smartphone (iPhone, Android, BlackBerry) o può essere originale e sviluppato da noi.
Ma c'è di più, le animazioni di transizione tra i vari pannelli che compongono la nostra applicazione sono anch'esse gestite in maniera naturale da Sencha Touch consentendoci con poco sforzo di sviluppare interfacce complesse che ricalcano molto fedelmente quelle delle applicazioni native.
Un mondo di dati
Come ogni buon framework che si rispetti anche Sencha Touch contiene una corposa libreria per la manipolazione, l'interpretazione e la memorizzazione di dati. Fanno parte di questa libreria oggetti per interagire con webservice remoti attraverso AJAX ma anche metodi per salvare le informazioni beneficiando delle nuove capacità offerte dall'HTML5.
HTML5? Si, grazie!
Sencha Touch trae vantaggio dalle nuove specifiche HTML5 e consente di memorizzare informazioni direttamente sul device dell'utente utilizzando
le API localStorage
. Sono inoltre a disposizione dello sviluppatore componenti Audio e Video che basano le loro funzionalità sui corrispettivi elementi HTML5.
Alla scoperta di Sencha:
Per meglio sperimentare le potenzialità offerte da questo framework creiamo una piccola applicazione da utilizzare per mostrare a video una
serie di appunti. Non è necessario possedere uno smartphone per seguire questo tutorial, è sufficiente infatti utilizzare Chrome che
condivide lo stesso layout engine (WebKit) dei browser installati sia su Android, che su iOs che su BlackBerry 6+.
Per prima cosa effettuiamo il download dell'archivio da questo indirizzo e decomprimiamolo, poi identifichiamo le cartelle resources/css
e resources/css-debug
ed insieme ai file sencha-touch-debug.js
e sencha-touch.js
copiamole all'interno di una cartella creata appositamente per il nostro progetto, appunti
, in modo da ottenere una struttura come la seguente:
Ora impostiamo le basi del documento HTML5 che conterrà l'applicazione: creiamo un file index.html
con questo contenuto:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Appunti</title>
<link rel="stylesheet" href="css-debug/sencha-touch.css" type="text/css">
<script type="text/javascript" src="js/sencha-touch-debug.js"></script>
<script type="text/javascript" src="js/application.js"></script>
</head>
<body></body>
</html>
Oltre alle classiche informazioni, come il charset ed il titolo, notiamo che abbiamo incluso il foglio di stile del tema da applicare alla nostra creazione.Inoltre è necessario richiamare il file Javascript che contiene l'intero framework. Il prefisso -debug
, applicato ad entrambi, denota che stiamo scegliendo dei file non compressi, utilissimi per curiosare
nel codice del framework.
Chiude l'elenco dei file application.js
, in questo file scriveremo l'applicazione; creiamolo quindi come documento vuoto all'interno
della cartella js
ed inseriamoci quanto segue:
new Ext.Application({
name: 'appunti',
launch: function() {
this.views.viewport = new Ext.Panel({
fullscreen: true,
html: 'Hello World!'
});
}
});
In queste poche righe abbiamo:
- definito una nuova applicazione con l'istruzione:
new Ext.Application
. In Sencha Touch un'applicazione non è nient'altro che il contenitore all'interno del quale risiede la logica, la parte di dati e quella di interfaccia del progetto su cui stiamo lavorando; - dato un nome all'applicazione con l'istruzione
name: 'appunti'
. Come possiamo notare Sencha Touch fa largo utilizzo di array associativi per definire proprietà e funzioni all'interno dei propri componenti. I nomi da usare per le chiavi, come 'name' o 'launch', sono elencati all'interno delle API del framework. - specificato con la chiave '
launch
' una funzione che verrà eseguita una singola volta contestualmente al lancio dell'applicazione. All'interno di questa funzione impostiamo come 'viewport
', il box nel quale verrà visualizzata tutta l'interfaccia, un singolo pannello al quale chiediamo di riempire l'intera finestra del browser ('fullscreen: true
') e di mostrare a video l'html 'Hello World!' (html: 'Hello World!'
).
Eseguendo nel browser otteniamo questo risultato:
Avevamo parlato di MVC?
Sencha Touch adotta il paradigma MVC come fondamenta sulle quali costruire le applicazioni. Per chi non lo conoscesse, questo acronimo indica una tecnica che identifica in una applicazione tre distinte sezioni, suddivise per responsabilità:
- model, trattano della parte legata alla persistenza del dato ed operano su di esso esponendo le logiche di business definite dall'applicazione,
- view, sono responsabili della generazione dell'interfaccia e della corretta formattazione del dato,
- controller, agiscono da registi e coordinano model e view per ottenere il risultato voluto.
Procediamo quindi con il creare all'interno della nostra struttura dati tre cartelle, una per sezione: 'js/models
', 'js/views
', 'js/controllers
'.
Il Model
Per prima cosa definiamo la nostra semplicissima struttura dati, creiamo un file dal nome frammento.js
all'interno della cartella js/models
:
appunti.models.Frammento = Ext.regModel("appunti.models.Frammento", {
fields: [
{name: "id", type: "int" },
{name: "titolo", type: "string" },
{name: "testo", type: "string" }
]
});
appunti.stores.frammenti = new Ext.data.Store({
model: "appunti.models.Frammento",
data: [
{id: 1, titolo: 'La gatta', testo: 'Tanto va la gatta al largo che ci lascia lo zampino'},
{id: 2, titolo: 'Di sera', testo: 'Rosso di sera bel tempo si spera'},
{id: 3, titolo: 'Da se', testo: 'Chi fa da se fa per tre'},
{id: 4, titolo: 'Lo zoppo', testo: 'Chi và con lo zoppo impara a zoppicare'}
]
});
Possiamo distinguere chiaramente due sezioni in questo codice:
- Nella prima, che comincia con
appunti.models.Frammento = Ext.regModel ('appunti.models.Frammento', {
, definiamo un modello di nome 'Frammento' atto a contenere dati strutturati in campi: id, titolo e testo. All'interno di questa porzione di codice se fosse stato necessario avremmo potuto inserire anche funzioni specifiche al comportamento di questo modello come ad esempio 'contaNumeroCaratteri
'. - Nella seconda, che parte da
appunti.stores.frammenti = new Ext.data.Store({
, creiamo un contenitore di elementi di tipo 'Frammento' e, attraverso la chiave 'data', lo popoliamo con alcuni di questi elementi.
Bene, sperimentiamo quanto abbiamo fatto, includiamo il file frammento.js all'interno di index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Appunti</title>
<link rel="stylesheet" href="css-debug/sencha-touch.css" type="text/css">
<script type="text/javascript" src="js/sencha-touch-debug.js"></script>
<script type="text/javascript" src="js/application.js"></script>
<script type="text/javascript" src="js/models/frammento.js"></script>
</head>
<body></body>
</html>
Ora lanciamo l'applicazione all'interno di Chrome e premiamo quindi Control-Shift-J
per accedere alla console javascript interattiva; eseguiamo all'interno di questo ambiente il seguente comando:
appunti.stores.frammenti.getAt(appunti.stores.frammenti.find('titolo',/sera/)).get('testo');
Quello che abbiamo appena eseguito è stato richiedere allo store 'frammenti' di cercare il primo elemento con titolo corrispondente all'espressione regolare '/sera/'; dopodiché abbiamo recuperato l'elemento indicato dall'indice ottenuto dalla ricerca e di questo abbiamo estratto il valore del campo 'testo'. Ecco il risultato che certifica il buon funzionamento del nostro modello:
Le View
Dobbiamo creare due view, una per l'elenco degli appunti ed una di dettaglio. Creiamo per primo l'elenco, aggiungiamo un file elenco.js
all'interno della cartella js/views
e popoliamolo come segue:
appunti.views.ElencoFrammenti = Ext.extend(Ext.Panel, {
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
title: 'Elenco Appunti'
}],
items: [{
xtype: 'list',
store: appunti.stores.frammenti,
itemTpl: '{titolo}',
onItemDisclosure: function (record) {
//Ext.dispatch({
// controller: appunti.controllers.frammenti,
// action: 'show',
// id: record.getId()
//});
}
}]
});
Notiamo che stiamo estendendo un Componente di tipo Pannello personalizzandolo con due chiavi:
- dockedItems esprimiamo in questo array un elenco di componenti che vogliamo 'agganciare' a specifiche sezioni dello schermo, in questo caso l'array contiene un singolo componente, di tipo 'toolbar', che deve essere posizionato in alto con titolo 'Elenco Appunti'
- items i componenti listati in questo elenco differiscono da dockedItems solo in quanto si dispongono al centro della pagina. Per questa view abbiamo inserito un componente di tipo 'list' legato allo store 'frammenti' che abbiamo definito precedentemente. Ogni elemento presente nello store verrà stampato nella lista in accordo con le regole specificate nella chiave 'itemTpl' (quindi solo il titolo). E' presente anche una funzione, legata alla chiave 'onItemDisclosure' che viene invocata da framework alla selezione di uno degli elementi della lista; abbiamo commentato per il momento il contenuto di questa funzione in quanto non siamo ancora pronti ad introdurre la navigazione nella nostra applicazione.
Pronti per sperimentare? Bene, prima però dobbiamo istruire il Viewport informandolo di caricare la view; approfittiamo di questa necessità per modificare leggermente la struttura di application.js come segue:
new Ext.Application({
name: 'appunti',
launchOnDomReady: function() {
appunti.views.viewport = new (Ext.extend(Ext.Panel, {
fullscreen: true,
layout: 'card',
cardSwitchAnimation: 'slide',
items: [ new appunti.views.ElencoFrammenti() ]
}))();
}
});
Ext.setup({
onReady: appunti.launchOnDomReady
});
Per evitare che la funzione associata a launch
venga chiamata prima che tutti gli altri file javascript siano stati caricati, generando così un errore, ne modifichiamo il comportamento subordinandone l'esecuzione ad una proprietà 'onReady' che Sencha ci mette a disposizione proprio con l'obiettivo di fornirci un punto sicuro dal quale eseguire questo genere di operazioni. onReady viene infatti invocata solo quando la pagina è stata completamente caricata con tutte le sue dipendenze esterne. Aggiungiamo inoltre una chiave cardSwitchAnimation
che ci tornerà utile fra poco.
Benissimo, ora siamo pronti: aggiorniamo index.html
includendo in coda anche il nuovo file di view ed eseguiamo il tutto nel browser (o, se disponibile, da un device mobile):
Sviluppiamo anche la seconda view, quella di dettaglio, creando un file 'dettaglio.js' all'interno di 'js/views' e completandolo col seguente codice:
appunti.views.DettaglioFrammenti = Ext.extend(Ext.Panel, {
dockedItems: [{
xtype: 'toolbar',
title: 'View contact',
items: [
{
text: 'Back',
ui: 'back',
listeners: {
'tap': function () {
Ext.dispatch({
controller: appunti.controllers.frammenti,
action: 'index',
animation: {type:'slide', direction:'right'}
});
}
}
}
]
}],
items: [{
tpl: ['
{testo}
']
}]
});
Avendo ormai interiorizzato questa strutturazione dei contenuti possiamo accorgerci che in questo caso la toolbar stessa ha degli items allegati, in particolare si tratta di un pulsante di back che al 'tap' invoca un'azione 'index' su di un controller, ancora di sviluppare, di nome 'appunti.controllers.frammenti'.
L'array di items del pannello contiene invece un solo elemento denotato dalla chiave tpl; il suo comportamento è lo stesso della chiave itemTpl vista in precedenza: il segnaposto {testo} verrà rimpiazzato in fase di visualizzazione con la proprietà 'testo' dell'elemento che passeremo a questa vista.
Bene, prima di passare ai controller aggiungiamo 'dettaglio.js' in coda alle altre inclusioni del file 'index.html' e all'elenco degli items del viewport nel file 'application.js', che dovrebbe quindi risultare così composto:
new Ext.Application({
name: 'appunti',
launchOnDomReady: function() {
appunti.views.viewport = new (Ext.extend(Ext.Panel, {
fullscreen: true,
layout: 'card',
cardSwitchAnimation: 'slide',
items: [
new appunti.views.ElencoFrammenti(),
new appunti.views.DettaglioFrammenti()
]
}))();
}
});
Ext.setup({
onReady: appunti.launchOnDomReady
});
Il controller
Rimuoviamo i segni di commento dal file 'elenco.js' e prepariamoci a concludere la nostra applicazione. Creiamo un file 'controllers/frammenti.js' che dovrà coordinare la risposta alle azioni di navigazione dell'utente: 'index' e 'show':
appunti.controllers.frammenti = new Ext.Controller({
index: function(options) {
var indice = appunti.views.viewport.getComponent(0);
appunti.views.viewport.setActiveItem(indice, options.animation);
},
show: function(options) {
var id = parseInt(options.id),
frammento = appunti.stores.frammenti.getById(id);
if (frammento) {
var dettaglio = appunti.views.viewport.getComponent(1);
dettaglio.getDockedItems()[0].setTitle(frammento.get('titolo'));
dettaglio.items.items[0].update(frammento.data)
appunti.views.viewport.setActiveItem(dettaglio, options.animation);
}
}
});
La funzione più importante di questo listato di codice è sicuramente appunti.views.viewport.setActiveItem(...
che si occupa di attivare l'animazione fra la vista corrente e quella passata come primo argomento alla funzione. Con il metodo appunti.views.viewport.getComponent(...
possiamo invece chiedere al viewport di ritornare la vista identificata dall'indice passato come argomento. All'interno di 'show' possiamo infine notare come il passaggio di parametri dalla view al controller avvenga attraverso la variabile options, anch'essa ricevuta come argomento.
Attraverso le due righe di codice:
dettaglio.getDockedItems()[0].setTitle(frammento.get('titolo'));
dettaglio.items.items[0].update(frammento.data)
andiamo invece ad aggiornare la view 'dettaglio.js' modificando il titolo e completando il template a fronte del record che stiamo per visualizzare.
Perfetto, aggiungiamo anche 'frammenti.js' all'elenco dei file richiesti da 'index.js' ed eseguiamo il tutto per gustarci la nostra prima applicazione sviluppata con Sencha Touch.
Conclusioni
Questo articolo dovrebbe assolvere il compito di incuriosirvi in merito alle potenzialità offerte da questo framework; se così è stato preparatevi: quanto mostrato è solo la punta dell'iceberg delle funzionalità offerte da Sencha Touch. Tematiche come la connessione a sorgenti di dati esterni, lo sviluppo di temi personalizzati, la gestione di componenti avanzati e l'utilizzo di caching sono solo alcuni dei punti di forza di questo framework che merita sicuramente un approfondimento. Solo un ultimo appunto prima di chiudere questo articolo: avete provato a sostituire il css della nostra applicazione con uno degli altri a disposizione ? Buona sperimentazione!