In questa lezione modificheremo l'app vista nella lezione sulle TableView
affinchè possa leggere una sorgente
dati JSON
per poterla visualizzare in formato tabellare.
La modifica consiste fondamentalmente in tre step: il primo
chiede ad un web service
una stringa in formato JSON, il secondo trasforma la stringa
JSON in un oggetto JavaScript (che vogliamo sia un array di
oggetti), il terzo
step sarà quello di visualizzare i dati nella TableView.
Lettura di un web service
L'SDK di Titanium mette a disposizione un oggetto client
HTTP che implementa le specifiche XMLHttpRequest.
Titanium.Network.HTTPClient,
questo il suo nome, è in grado di effettuare richieste HTTP sia
sincrone che asincrone.
La struttura di una richiesta HTTP, effettuato con HTTPClient,
è la seguente:
// url di un servizio json di esempio
var url = "http://ip.jsontest.com/";
// 1) creazione dell'oggetto
var client = Ti.Network.createHTTPClient({
// callback chiamata quando abbiamo dei dati di risposta
onload : function(e) {/* callback */},
// callback chiamata quando c'è un errore
onerror : function(e) {/* callback*/},
});
// 2) definizione della richiesta
client.open("GET", url);
// 3) invio della richiesta
client.send();
Osserviamo come durante la creazione dell'oggetto HTTPClient
dobbiamo definire le funzioni di callback onload e onerror,
necessarie per gestire sia la ricezione dei dati che la notifica
di un errore. Il client è in grado di gestire anche altri eventi,
per esempio il progresso dell'upload e del download
di file o il cambio di stato, e per ognuno di questi eventi deve
essere definita una callback apposita da assegnare,
rispettivamente, alle proprietà onsendstream, ondatastream
e onreadystatechange.
Non essendo un oggetto dell'interfaccia utente, HTTPClient
non ha una sua sintassi XML da definire nella view.
Utilizzeremo per il nostro nuovo esempio, il prezzo dei
Bitoin fornito da Bitcoin Charts,
come sorgente dati. I Bitcoin sono una moneta virtuale il
cui valore risente di forti oscillazioni ed il cui mercato è
aperto 24 ore su 24. Inoltre, Bitcoin Charts fornisce in
tempo reale le quotazioni su tutti i principali mercati e per
tutte le principali valute in formato JSON, risultando ideale per
il nostro esempio.
Come prima cosa salveremo la URL del nostro servizio in una
variabile globale. Alloy fornisce una struttura dati per
memorizzare variabili globali, cioè accessibili
in qualunque parte del codice senza dover dichiarare o importare
moduli. Questa struttura si chiama Alloy.Globals,
che come il nome lascia intendere è accessibile globalmente in
tutto il progetto ed è ideale per memorizzare variabili e funzioni
globali.
Il file dove andremo a definire la variabile globale Alloy.Globals.url
è app/alloy.js, perchè il suo contenuto sarà eseguito
prima di qualsiasi altro controller, e questo lo rende
il punto migliore in cui inizializzare gli oggetti globali.
// The contents of this file will be executed before any of
// your view controllers are ever executed, including the index.
// You have access to all functionality on the `Alloy` namespace.
//
// This is a great place to do any initialization for your app
// or create any global variables/functions that you'd like to
// make available throughout your app. You can easily make things
// accessible globally by attaching them to the `Alloy.Globals`
// object. For example:
//
// Alloy.Globals.someGlobalFunction = function(){};
Alloy.Globals.url = "http://api.bitcoincharts.com/v1/markets.json";
Una volta definita l'URL del servizio in alloy.js,
passiamo a modificare il file index.js creando l'oggetto HTTPClient
e definendo le callback onload e onerror. Fatto ciò,
eseguiremo la richiesta HTTP GET verso l'URL che
abbiamo appena definito in alloy.js.
var client = Ti.Network.createHTTPClient({
onload : function(e) {
var json = JSON.parse(this.responseText);
onSuccess(json);
},
onerror : function(e) {
Ti.API.debug(e.error);
alert('error ' + JSON.stringify(e.error));
},
timeout : 5000 // in milliseconds
});
La callback onload viene richiamata alla ricezione dei
dati, contenuti in this.responseText.
Parsing di un oggetto JSON
L'oggetto HTTPClient ha tre proprietà a disposizione
per leggere i contenuti: responseText di tipo stringa, responseXML
di tipo Titanium.XML.Document
(XML) e responseData di tipo Titanium.Blob
(binario). Non esistendo una proprietà apposita per gli oggetti
JavaScript, il metodo più semplice è quello di convertire
il contenuto testuale in formato JSON direttamente in un oggetto
JavaScript. Per questo si utilizza la funzione JSON.parse,
il cui output, se il parametro d'ingresso è ben formato, è un
array o un oggetto.
var json = JSON.parse(this.responseText);
In questo caso la variabile json conterrà un array di
oggetti la cui struttura è simile alla seguente:
{
volume: 19673.663200000000,
latest_trade: 1409160407,
bid: 3136.570000000000,
high: 3198.000000000000,
currency: "CNY",
currency_volume: 62016203.271829000000,
ask: 3136.820000000000,
close: 3137.100000000000,
avg: 3152.244838258133848707951857,
symbol: "btcnCNY",
low: 3112.130000000000
}
Siamo interessati a visualizzare alcune proprietà di questo
oggetto ed in particolare ask, bid, avg, symbol
e currency. ask e bid si riferiscono
rispettivamente al prezzo a cui posso comprare o vendere un
Bitcoin, avg rappresenta il prezzo medio, symbol il servizio di trading e currency la valuta con
cui posso effettuare le operazioni.
La lettura di un web service è un'operazione asincrona,
per cui dovremo disegnare la nostra tabella solo dopo aver
ricevuto i dati. Questo è il motivo per cui dopo aver fatto il
parsing del contenuto in formato JSON, dobbiamo passare l'oggetto
ad una funzione onSuccess. Questa funzione è quella che
disegnerà la tabella, ed il codice è il seguente:
function onSuccess(items) {
// array di righe Ti.UI.TableViewRow
var rows = [];
for (var i in items) {
var item = items[i];
// creazione di un oggetto TableViewRow passandogli un parametro
var row = Alloy.createController('row', item).getView();
// aggiungo le proprietà ask, bid per poterla recuperare in seguito (vedi (*))
row.ask = item.ask;
row.bid = item.bid;
// aggiunta dell'oggetto all'array
rows.push(row);
}
$.list.data = rows;
}
Rispetto all'esempio della lezione sulle TableView,
la differenza che salta all'occhio è che questa volta il codice
è incapsulato in una funzione e sono cambiate le proprietà
dell'oggetto che si vuole "mettere da parte" per quando verrà
cliccata una riga della tabella (ask e bid al posto
di url). Analogamente, anche la callback onClick
deve essere modificata: invece di aprire il browser, mostrerà in
un handler i valori di ask e bid.
function onClick(e) {
// (*)
var ask = e.rowData.ask;
var bid = e.rowData.bid;
alert("Ask :" + Math.round(ask*100)/100 + "\nBid :" + Math.round(bid*100)/100);
}
Anche in questo caso, l'ultima istruzione del controller index.js
è l'apertura della view.
// apro la vista
$.index.open();
Visualizzazione dei dati
Rispetto all'esempio visto per le TableView, essendo cambiata la natura dell'oggetto che rappresenta la
nostra sorgente dati, dovremo modificare anche il controller row
relativo alle righe della TableView. Per semplicità non
modifichiamo la view: visto che le due etichette si chiamano title
e description possiamo mantenerle cambiando semplicemente
il valore della loro proprietà text nel controller row.js.
// ricevo l'argomento
var args = arguments[0] || {};
// assegnazione ai widgets
$.title.text = args.symbol;
$.description.text = Math.round(args.avg*100)/100 + " " + args.currency;
Il risultato, visto dal simulatore iOS, sarà il
seguente:
La tabella contiene molti dati che possiamo filtrare.
Per esempio potremmo visualizzare solo quei mercati dove i bitcoin
sono visualizzati in dollari (USD) ed eliminare anche quelle voci
che stanno a 0 probabilmente perchè sono mercati chiusi o
semplicemente i dati non sono pervenuti.
Per modificare i dati possiamo intervenire a valle, dentro il
ciclo for di onSuccess, aggiungendo una condizione,
oppure sfruttare il fatto che Alloy utilizza underscore.js e quindi filtrare a monte i
dati appena ricevuti.
In questo caso la callback di onload cambierà in questo
modo:
onload : function(e) {
var json = JSON.parse(this.responseText);
// condizione di filtro: solo USD e con prezzi disponibili
var jsonfiltered = _.filter(json, function(o) {
return o.currency == "USD" && o.volume;
});
Ti.API.info(JSON.stringify(jsonfiltered));
onSuccess(jsonfiltered);
},
Dulcis in fundo, cambiamo leggermente lo stile alle righe della
tabella rendendo più leggibile il prezzo medio di un singolo
bitcoin. Il nuovo row.tss diventa simile al seguente:
"#info": {
height: 50
}
"#title": {
left: 5
}
"#description": {
right: 5,
font: {
fontSize: 15,
fontWeight: "bold"
}
}
".labels": {
font: {
fontSize: 10
}
}
Con questo .tss, il campo description apparirà
con un font più grande e grassetto nonostante sia definito anche
come oggetto di classe labels. Questo perchè, nel caso di
sovrapposizione, gli stili legati alla proprietà id
prevalgono su quelli con proprietà class.
Il risultato finale sarà il seguente: