function online(event) { statusElem.className = navigator.onLine ? 'online' : 'offline'; statusElem.innerHTML = navigator.onLine ? 'online' : 'offline'; state.innerHTML += '<li>New event: ' + event.type + '</li>'; } window.addEventListener('online', online); window.addEventListener('offline', online);
Principle: inclure un fichier "manifeste" dans chaque page HTML qui doit être cachée.
<html manifest="myCache.appcache"/>
Nouveau type MIME :
AddType text/cache-manifest .appcache
CACHE MANIFEST clock.html clock.css clock.js
Tiré de cet exemple de la doc W3C sur la cache API.
La première ligne (CACHE MANIFEST) est obligatoire,
Chaque fichier qui doit être caché doit être inclu individuellement (pas de wildcards),
La page HTML elle-même est inclue par défaut...
Quand un navigateur trouve un fichier manifeste, il ajoute chaque fichier listé dans le cache, alors, il prendra dorénavant ces fichiers dans le cache...
Sauf si le fichier manifeste côté serveur est mis à jour (date changée).
Bonne pratique : ajouter un timestamp / commentaire dans le manifeste à chaque update.
Imaginez un formulaire login / password , ne doit pas être caché ou affiché en mode offline.
Le manifeste a des directives pour celà :
Note: wildcards autorisées dans NETWORK et FALLBACK, pas avec la directive CACHE .
CACHE MANIFEST CACHE: #images /images/image1.png /images/image2.png #pages /pages/page1.html /pages/page2.html #CSS /style/style.css #scripts /js/script.js # / = any resource that is not available in the cache FALLBACK: / /offline.html NETWORK: login.html
... NETWORK: * FALLBACK: /login.html /offline.html
Cliquer l'image suivante pour un exemple réel...
Habituellement : 5 megas pour toute persistence client-side, infinie si l'application vient du Chrome AppStore, du Windows Store,
Parfois configurable (Opera), une "quota API" est en route,
Ce pirate (par Andrés Lucero) surveille vos données !
Il est possible de mettre à jour le cache par programme :
var appCache = window.applicationCache; appCache.update(); // Attempt to update the user's cache. if (appCache.status == window.applicationCache.UPDATEREADY) { appCache.swapCache(); // The fetch was successful, swap in the new cache. }
L'API est très complète et propose d'autres événements : onerror, onupdate, ondownloading, onprogress, etc.
// Check if a new cache is available on page load. window.addEventListener('load', function(e) { window.applicationCache.addEventListener('updateready', function(e) { if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { // Browser downloaded a new app cache. // Swap it in and reload the page to get the new hotness. window.applicationCache.swapCache(); if (confirm('Une nouvelle version du site est disponible : mise à jour du cache.')) { window.location.reload(); } } else { // Manifest didn't changed. Nothing new to server. } }, false); }, false);
La quota API est déjà disponible (seul Chrome l'implémente complètement pour le moment)
Un exemple de démonstration sur jsbin.com
function showFreeSpace() { //the type can be either TEMPORARY or PERSISTENT webkitStorageInfo.queryUsageAndQuota(webkitStorageInfo.TEMPORARY, onSuccess, onError); } function onSuccess(usedSpace, remainingSpace) { var displaySpace = document.querySelector("#space"); displaySpace.innerHTML = "Used space = " + usedSpace + ", remaining space = " + remainingSpace; } function onError(error) { console.log('Error', error); }
Lire : Managing HTML5 Offline Storage and using the quota API sur le site des développeurs Google.
Lire : "Working with quota on mobile browsers", article de HTML5rocks.com
Ce formulaire sauvegarde son contenu automatiquement. Essayez de recharger cette page !
Remarque :
avec certains navigateurs, cette page doit être accédé par
http:// pas par file://
// store data localStorage.firstName = "Michel"; // retrieve data var firstName = localStorage.firstName;
<head> <script> // store data localStorage.lastName = "Buffa"; localStorage.firstName = "Michel"; function getData() { // retrieve data document.querySelector("#lastName").innerHTML = localStorage.lastName; document.querySelector("#firstName").innerHTML = localStorage.firstName; } </script> </head> <body onload="getData()"> <h1>Data retrieved from localStorage</h1> <ul> <li>Last name : <span id="lastName"></span></li> <li>First name : <span id="firstName"></span></li> </ul>
With Chrome dev tools:
<type=file ... />
Select one or more files: <input type="file" id="input"> ... var selectedFile = document.getElementById('input').files[0]; var name = selectedFile.name; var size = selectedFile.size; var type = selectedFile.type; var date = selectedFile.lastModifiedDate; ...
<!DOCTYPE html> <html> <head> <script> function displayFirstSelectedFile() { var selectedFile = document.getElementById('input').files[0]; document.querySelector("#singleName").innerHTML = selectedFile.name; document.querySelector("#singleSize").innerHTML = selectedFile.size + " bytes"; document.querySelector("#singleType").innerHTML = selectedFile.type; } </script> </head> <body> Select one or more files: <input type="file" id="input"> <br/> <button onclick="displayFirstSelectedFile()">Click me to see details about the selected files</button> <ul> <li>First selected file name is: <span id="singleName"></span></li> <li>First selected file name is: <span id="singleSize"></span></li> <li>First selected file name is: <span id="singleType"></span></li> </ul> </body> </html>
L'interface FileReader :
FileReader reader = new FileReader();
3 methodes:
reader.readAsText(File, opt_encoding)
.reader.readAsDataURL(File)
reader.readAsArrayBuffer(File)
Un data URL est un URL qui inclut le contenu et le type en même temps. Voici un exemple de carré rouge, sous forme de data URL. Copiez et collez le dans la barre d'adresse d'un navigateur, vous devriez voir le carré rouge.
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==
<img width=50 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO 9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red square" />
var reader = new FileReader(); reader.onload = function(e) { // Render thumbnail. e.target.result is the data URL of the image var span = document.createElement('span'); span.innerHTML = "<img class='thumb' src='" + e.target.result + "'/>"; document.getElementById('list').insertBefore(span, null); }; // Read in the image file as a data URL. reader.readAsDataURL(f);
var reader = new FileReader(); //read the file content. reader.onload = function(e) { alert('Text file content :\n\n' + e.target.result); }; // Read the file as text reader.readAsText(f);
// User selects file, read it as an ArrayBuffer and pass to the API. var fileInput = document.querySelector('input[type="file"]'); fileInput.addEventListener('change', function(e) { var reader = new FileReader(); reader.onload = function(e) { initSound(this.result); }; // THIS IS THE INTERESTING PART! reader.readAsArrayBuffer(this.files[0]); }, false);
Ce code aussi avec l'exemple précédent (lecture d'un fichier binaire) :
// Load sound sample from a URL with XHR2 as an ArrayBuffer. function loadSoundFile(url) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer'; // THIS IS NEW WITH HTML5! xhr.onload = function(e) { initSound(this.response); // this.response is an ArrayBuffer // a binary file, native format }; xhr.send(); }
<input id="file" type="file" /> <script> var fileInput = document.querySelector('#file'); fileInput.onchange = function() { var xhr = new XMLHttpRequest(); xhr.open('POST', 'upload.html'); // With FormData, POST is mandatory xhr.onload = function() { alert('Upload complete!'); }; var form = new FormData(); form.append('file', fileInput.files[0]); // send the request xhr.send(form); }; </script>
loaded
et total
de l'évènement passé à upload.onprogress.<progress id="progress"></progress> <script> var fileInput = document.querySelector('#file'), progress = document.querySelector('#progress'); fileInput.onchange = function() { var xhr = new XMLHttpRequest(); xhr.open('POST', 'upload.html'); xhr.upload.onprogress = function(e) { progress.value = e.loaded; progress.max = e.total; }; xhr.onload = function() { alert('Upload complete!'); }; var form = new FormData(); form.append('file', fileInput.files[0]); xhr.send(form); }; </script>
Faites un drag sur un élément !
<ol ondragstart="dragStartHandler(event)"> <li draggable="true" data-value="fruit-apple">Apples</li> <li draggable="true" data-value="fruit-orange">Oranges</li> <li draggable="true" data-value="fruit-pear">Pears</li> </ol>
function dragStartHandler(event) { console.log('dragstart event, target: ' + event.target); // Copy in the drag'n'drop clipboard the value of the data* // attribute of the target, with a type "Fruit". event.dataTransfer.setData("Fruit", event.target.dataset.value); }
<div ondragover="return false" ondrop="dropHandler(event);"> Drop your favorite fruits below: <ol id="droppedFruits"></ol> </div>
function dropHandler(event) { console.log('drop event, target: ' + event.target); ... // get the data from the drag'n'drop clipboard, with a // type="Fruit" var data = event.dataTransfer.getData("Fruit"); ... }
drag'n'droppez un élément !
dropEffect
et effectAllowed
de event.dataTransfer.Valeurs possibles: copy, move, link, etc. ou changer l'apparence du curseur (image custom par ex.)
Pour applications en provenance des "stores" (Chrome Store ou Windows Store), ou accès concurrents aux données par de multiples Workers.
C'est un object store JS,
API asynchrone, pas très compliquée mais non triviale.
La plupart des exemples sur le Web sont obsolètes et ne fonctionnent pas.
Voyons quelques exemples qui fonctionnent !
WebSQL est abandonné: spécification plus en maintenance.
FileSystem et FileWriter APIs: uniquement dans Chrome pour le moment, La spécification est stable mais peut encore changer.
Plus de détails dans :
Lire why there is no planned filesystem APIs in Firefox.
Mais Eric Bilderman (Google Chrome) a écrit une librairie JavaScript qui émule ces APIs à 99%, avec IndexedDB.
Mozilla propose au W3C une FileHandle API à la place...
Microsoft fait tout avec des blobs dans IndexedDB
Alors... pour FileWriter et FileSystem API il est urgent d'attendre un peu.
Vous avez besoin de transactions, de faire des recherches dans de gros volumes de données, alors utilisez IndexedDB,
Vous devez simplement gérer des paires clé/valeur, utilisez localStorage/sessionStorage.
Applications offline, accès rapide : aujourd'hui, utilisez la cache API, demain Service Worker, ou mettez vous à IndexedDB.
Vous rêvez de faire du SQL , utilisez WebSQL. Oops, non, pardon, c'est mort WebSQL...