-
Notifications
You must be signed in to change notification settings - Fork 4
05.Webstorage Communication Networking API
In questa esercitazione vedremo come funzionano e come applicare le Webstorage API, combinandole con le nuove XHR2 e le Networking API.
Utilizzeremo l'oggetto localstorage per storicizzare dati all'interno del browser. Vedremo come funziona il caricamento di un'immagine trascinandola all'interno della pagina web dal proprio desktop. Alla fine utilizzeremo le librerie per controllare lo stato della connessione per capire come prevenire eventuali perdite di dati.
Per avere una situazione stabile e funzionante puoi aggiornare il tuo sorgente direttamente da questo comando:
# !bash
# git checkout webstorage-dev
Git tiene traccia di tutte le modifiche fatte in locale, quindi per cambiare ramo dobbiamo prima annullare tutte le modifiche fatte sui file che abbiamo modificato con il comando:
# !bash
# git checkout *
Se vuoi mantenere le modifiche fatte, fai commit delle tue modifiche oppure clona il progetto in una nuova cartella! se invece hai creato nuovi file, devi prima cancellarli.
Se non vuoi utilizzare git puoi scaricare l'archivio zip da qui: https://github.com/marcocasario/codemotion2014/tree/webstorage-dev
La nostra pagina dei contatti deve contenere nella parte centrale il form per l'inserimento dei dati anagrafici. Aggiungiamo il form utilizzando i nuovi input type di HTML5:
Elenco dei campi:
- input type text class="input" id="firstname" name="firstname" il campo deve essere obbligatorio, possiamo eventualmente anche aggiungere anche l'autofocus
- input type email class="input" id="email" name="email" il campo deve essere obbligatorio
- input type data class="input" id="born" name="born" il campo deve essere obbligatorio
- input type text class="input" id="topic" name="topic" il campo deve essere obbligatorio, in questo caso implementare anche un oggetto di tipo datalist
esempio:
<input class="input" id="topic" name="topic" type="text" list="topic_list" required>
<datalist id="topic_list">
<option>Milano</option>
<option>Roma</option>
<option>Madrid</option>
<option>Tel Aviv</option>
</datalist>
- textarea class="textarea" id="note" name="note" il campo deve essere obbligatorio
per maggiori informazioni sui nuovi input type di HTML5 puoi consultare questo link http://www.w3.org/html/wg/drafts/html/master/forms.html
L'esempio riportato di seguito ha la struttura del form da inserire all'interno della nostra pagina contact.html.
Il form non ha i campi per l'inserimento dei dati, sostituisci i commenti che trovi all'interno dell'esempio con gli input del form ().
<form id="form" class="form_box">
<fieldset class="fieldset">
<ol>
<li class="left">
<label class="label" for="firstname">Name *</label><br>
<!-- input => firstname -->
</li>
<li class="right">
<label class="label" for="email">Email *</label><br>
<!-- input => email -->
</li>
</ol>
</fieldset>
<fieldset class="fieldset">
<ol>
<li class="left">
<label class="label" for="born">Date of birth *</label><br>
<!-- input => born -->
</li>
<li class="right">
<label class="label" for="topic">Topic *</label><br>
<!-- datalist => topic -->
</li>
</ol>
</fieldset>
<fieldset class="fieldset">
<ol>
<li class="left">
<label class="label" for="note">Note *</label><br>
<!-- textarea => note -->
</li>
</ol>
</fieldset>
<fieldset class="fieldset">
<button class="submit" type=submit>Send</button>
</fieldset>
</form>
Aggiorniamo il browser, il risultato dovrebbe essere simile a questo:
*Se necessario, sistema i valori di margin e padding del form e dei suoi elementi interni.
/* Gestisce il salvataggio al click dell'utente sul tasto send del form */
function saveUser (e){
e.preventDefault();
//se i dati vengono popolati con successo, inviamo la richiesta al servizio
if ( userModel.setData($(this).serializeArray())){
saveLocalstorageData();
}
}
La funzione saveUser, viene chiamata al click sul tasto send (utilizzando jQuery). La funzione saveUser() quando invocata richiama le seguenti funzioni:
userModel.setData(); saveLocalStorageData();
e.preventDefault() serve per bloccare il comportamento di default del browser al submit del form. In alcuni casi (es: IE < 9) potrebbe essere necessario anche un return false;
Aggiungiamo la seconda nostra funzione al file contact.js
/* contact.js */
/* Oggetto UserModel() */
function UserModel() {
//controlla se la variabile data è valorizzata altrimenti la inizializza come oggetto vuoto
if ( !isNotNull( data )){
var data = {};
}
//metodo setData()
this.setData = function ( newData ){
if (newData && newData.length > 0){
//se newData e' di tipo array (serializzato dal form)
//valorizziamo data con tutti gli elementi del form che sono stati inviati
for (var i = 0; i < newData.length; i++){
data[newData[i].name] = newData[i].value;
}
}else if ( isNotNull( newData ) ){
//se newData non e' un array valorizziamo data con newData
data = newData;
}else {
//altrimenti mostriamo un errore in console
console.log('Errore....');
return false;
}
return true;
};
//Restituisce l'oggetto data
this.getData = function (){
return data;
};
return this;
}
Questa funzione utilizza un metodo non nativo di Javascript isNotNull, questo metodo deve essere definito all'interno del file js/main.js.
//controlla se l'oggetto passato non e' vuoto
function isNotNull(value) {
return (typeof value !== undefined && value !== null);
}
L'oggetto UserModel espone due metodi:
- setSata
- getData
Il primo valorizza l'oggetto data, il secondo lo restituisce.
Aggiungiamo la terza funzione al file contact.js, questa funzione scrive i dati all'interno del browser.
/* contact.js */
//salva i dati nel LocalStorage
function saveLocalstorageData( ){
console.log('Salvataggio dei dati utente', userModel);
//utilizza l'oggetto localStorage per salvare i dati all'interno del browser
localStorage.setItem('userData', JSON.stringify(userModel.getData()))
return true;
}
L'oggetto localStorage espone due metodi usati da questo esercizio:
- setItem
- getItem
In questa funzione, utilizzeremo localStorage.setItem(), è possibile sperimentare cambiando l'oggetto localstorage con sessionStorage.
Per maggiori informazioni sulle API: http://www.w3.org/TR/webstorage/
A questo punto non ci resta che inizializzare il nostro oggetto UserModel ed aggiungere un evento al submit del form.
//inizializziamo il modello
var userModel = new UserModel;
//aggiungiamo l'evento al submit
$('#form').submit(saveUser);
Riempi il form e salva. Apriamo i Developer Tools di chrome, facciamo click sulla scheda Resources. Il risultato dovrebbe essere molto simile a questo:
Una volta salvati i dati, quando rientriamo all'interno del form ancora non vengono recuperati.
Per recuperare i dati è molto semplice, creiamo una nuova funzione, questa sarà richiamata ogni volta che la pagina verrà aperta:
function getUserInformation (){
//richiediamo i dati all'oggetto e popoliamo il model
var response = localStorage.getItem('userData');
userModel.setData( JSON.parse(response) );
setFormData( userModel );
}
Aggiungiamo la funzione che si occupa quindi di popolare il form direttamente dai localStorage.
function setFormData (){
//recuperiamo le informazioni dal datamodel
var data = userModel.getData();
//se l'oggetto è valorizzato aggiungiamo i valori agli input del nostro form
if (isNotNull( data )) {
$('#firstname').val( data.firstname );
$('#email').val( data.email );
$('#born').val( data.born );
$('#topic').val( data.topic );
$('#note').val( data.note );
}
}
Il metodo getUser() può essere richiamato subito dopo la creazione del model
//inizializziamo il modello
...
//recuperiamo i dati dall'oggetto localStorage
getUserInformation();
//aggiungiamo l'evento al submit
...
Una volta salvati i dati all'interno dell'oggetto sessionStorage, al caricamento della pagina se questi dati sono presenti, il codice che abbiamo appena implementato li recupera e li inserisce per noi all'interno del form.
Il codice seguente serve per implementare un campo upload immagine, inseriscilo prima del bottone send.
<fieldset class="fieldset">
<ol>
<li class="left" id="upload">
<label class="label" for="fileupload">Allega la tua foto</label><br>
<div id="holder"></div>
<p id="upload" class="hidden"><label>Drag & drop non supportato, ma puoi comunque inviare la tua foto:<br><input type="file" id="fileupload" name="fileupload"></label></p>
<p id="filereader">File API & FileReader API non supportati</p>
<p id="formdata">XHR2's FormData non è supportato</p>
<p id="progress">XHR2's upload progress non supportato</p>
<p>Trasferimento: <progress id="uploadprogress" min="0" max="100" value="0">0</progress></p>
<p>Trascina e rilascia un'immagine dal tuo desktop nell'area tratteggiata.</p>
</li>
</ol>
</fieldset>
All'interno del codice troviamo un nuovo fieldset che serve per ospitare il drag&drop. L'elemento più importante è holder, questa sarà l'area visibile dall'utente dove poter rilasciare l'oggetto trascinato dal proprio browser. Gli altri elementi servono per effettuare dei test lato javascript e mostrare i rispettivi messaggi, diversi in base al browser che stiamo usando.
Aggiungiamo i nuovi stili al nostro file css:
/* Contact Page */
#holder { border: 10px dashed #ccc; min-height: 150px; margin: 20px auto;}
#holder.hover { border: 10px dashed #0c0; }
#holder img { display: block; margin: 10px auto; }
#holder p { margin: 10px; font-size: 14px; }
il primo passaggio necessario è quello di aggiungere tutti i riferimenti agli oggetti html
var holder = document.getElementById('holder'),
tests = {
filereader: typeof FileReader != 'undefined',
dnd: 'draggable' in document.createElement('span'),
formdata: !!window.FormData,
progress: "upload" in new XMLHttpRequest
},
support = {
filereader: document.getElementById('filereader'),
formdata: document.getElementById('formdata'),
progress: document.getElementById('progress')
},
acceptedTypes = {
'image/png': true,
'image/jpeg': true,
'image/gif': true
},
progress = document.getElementById('uploadprogress'),
fileupload = document.getElementById('upload');
Aggiungiamo quindi il codice che avrà il compito di controllare le funzionalità del browser
"filereader formdata progress".split(' ').forEach(function (api) {
if (tests[api] === false) {
support[api].className = 'fail';
} else {
// FFS. I could have done el.hidden = true, but IE doesn't support
// hidden, so I tried to create a polyfill that would extend the
// Element.prototype, but then IE10 doesn't even give me access
// to the Element object. Brilliant.
support[api].className = 'hidden';
}
});
function readfiles(files) {
debugger;
var formData = tests.formdata ? new FormData() : null;
for (var i = 0; i < files.length; i++) {
if (tests.formdata) formData.append('file', files[i]);
previewfile(files[i]);
}
// now post a new XHR request
if (tests.formdata) {
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://comtaste.com/codemotion/upload_file.php');
xhr.onload = function() {
progress.value = progress.innerHTML = 100;
};
if (tests.progress) {
xhr.upload.onprogress = function (event) {
if (event.lengthComputable) {
var complete = (event.loaded / event.total * 100 | 0);
progress.value = progress.innerHTML = complete;
}
}
}
xhr.send(formData);
}
}
Implementiamo il codice per creare l'anteprima da mostrare all'utente:
function previewfile(file) {
if (tests.filereader === true && acceptedTypes[file.type] === true) {
var reader = new FileReader();
reader.onload = function (event) {
var image = new Image();
image.src = event.target.result;
image.width = 250; // a fake resize
holder.appendChild(image);
};
reader.readAsDataURL(file);
} else {
holder.innerHTML += '<p>Uploaded ' + file.name + ' ' + (file.size ? (file.size/1024|0) + 'K' : '');
console.log(file);
}
}
Come ultimo passaggio, aggiungiamo l'evento:
if (tests.dnd) {
holder.ondragover = function () { this.className = 'hover'; return false; };
holder.ondragend = function () { this.className = ''; return false; };
holder.ondrop = function (e) {
this.className = '';
e.preventDefault();
readfiles(e.dataTransfer.files);
}
} else {
fileupload.className = 'hidden';
fileupload.querySelector('input').onchange = function () {
readfiles(this.files);
};
}
Ed ecco il risultato che dovremmo ottenere:
Aggiungiamo il nuovo bottone all'interno del nostro form:
<fieldset class="fieldset">
<button class="submit" type=submit>Send</button>
<button class="save" type=submit>Save</button>
</fieldset>
Aggiungiamo un nuovo css per gestire i due bottoni diversi
.main_section .form_box .save { width: 120px; height: 37px; font-size: 13px; line-height: 37px; background: #f39c12; border: 1px solid #d35400; border-radius: 4px; padding: 0 15px; color: #fff; text-transform: uppercase; margin: 10px 0 40px; float: right; cursor: pointer; outline: none; }
.main_section .form_box .save:hover { background: #d35400; }
Aggiungiamo due nuove funzioni al js/main.js per nascondere e mostrare dinamicamente gli oggetti
//show element
function show( dom ){
if( isNotNull(dom) && typeof dom === 'object'){
dom.removeClass('hide');
}
}
//hide element
function hide( dom ){
if( isNotNull(dom) && typeof dom === 'object'){
dom.addClass('hide');
}
}
Aggiungiamo una nuova funzione che sarà richiamata al cambio di stato delle interfacce di rete.
/* js/contact.js */
/* NET API */
function changeNetworkStatus(){
if (navigator.onLine) {
hide($('.save'));
show($('.submit'));
}else{
hide($('.submit'));
show($('.save'));
}
}
window.addEventListener("offline", function(e) {
changeNetworkStatus();
});
window.addEventListener("online", function(e) {
changeNetworkStatus();
});
Infine, richiamiamo la funzione anche all'apertura della pagina web.
changeNetworkStatus();
A questo punto se proviamo a staccare e riattaccare la nostra connessione il risultato ottenuto dovrebbe essere il seguente: