Slobodan Stojanovic
CTO of Cloud Horizon and JS Belgrade Meetup organizer. Programmer, mostly JS.
Why would anyone want an offline web app?
"Web" and "online" are two closely associated terms, downright synonymous to many people. So why on earth would we talk about "offline" web technologies, and what does the term even mean?
...
Everyone's happy, nothing is broken or smudged. There is infinite wi-fi with infinite bandwidth, no batteries run out, no traffic no delays.
...
How to build an offline web application?
navigator.onLine
navigator.onLine is a property that maintains a true/false value (true for online, false for offline). This property is updated whenever the user switches into "Offline Mode"
"online" and "offline"
events
These two events are fired on the <body> of each page when the browser switches between online and offline mode. Additionally, the events bubble up from document.body, to document, ending at window.
<html manifest="example.appcache">
...
</html>
index.html
example.appcache
CACHE MANIFEST
# v1 2015/12/26 <- This is just a comment
index.html
cache.html
style.css
image1.png
# Use from network if available
NETWORK:
/api
# Fallback content
FALLBACK:
/foo/bar fallback.html
window.applicationCache
const appCache = window.applicationCache;
switch (appCache.status) {
case appCache.UNCACHED: // UNCACHED == 0
return 'UNCACHED';
break;
case appCache.IDLE: // IDLE == 1
return 'IDLE';
break;
case appCache.CHECKING: // CHECKING == 2
return 'CHECKING';
break;
case appCache.DOWNLOADING: // DOWNLOADING == 3
return 'DOWNLOADING';
break;
case appCache.UPDATEREADY: // UPDATEREADY == 4
return 'UPDATEREADY';
break;
case appCache.OBSOLETE: // OBSOLETE == 5
return 'OBSOLETE';
break;
default:
return 'UKNOWN CACHE STATUS';
break;
};
events:
// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);
// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);
// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);
// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);
// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);
// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);
// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);
// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);
"Application Cache is a Douchebag"
Deprecated
This feature has been removed from the Web standards. They recommend using Service Workers instead.
Service workers essentially act as proxy servers that sit between web applications, and the browser and network (when available.)
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'/',
'/styles/main.css',
'/script/main.js'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
Cache resources
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
Cache requests
this.addEventListener('install', function(event) {
event.waitUntil(
caches.open('v2').then(function(cache) {
return cache.addAll([
'/sw-test/',
'/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
// …
// include other new resources for the new version...
]);
});
);
});
Updating service worker
this.addEventListener('activate', function(event) {
var cacheWhitelist = ['v2'];
event.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (cacheWhitelist.indexOf(key) === -1) {
return caches.delete(key);
}
}));
})
);
});
Deleting old cache
The localStorage property allows you to access a local Storage object.
localStorage is similar to sessionStorage. The only difference is that, while data stored inlocalStorage has no expiration time, data stored in sessionStorage gets cleared when the browsing session ends - that is, when the browser is closed.
Deprecated
Since November 18, 2010, the W3C announced that Web SQL database is a deprecated specification.
IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs.
It uses indexes to enable high performance searches.
It lets you store and retrieve objects that are indexed with a key.
Any objects supported by the structured clone algorithm can be stored.
The structured clone algorithm is a new algorithm defined by the HTML5 specification for serializing complex JavaScript objects.
The algorithm, in essence, walks over all the fields of the original object, duplicating the values of each field into a new object.
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB ||
window.msIndexedDB;
// DON'T use "var indexedDB = ..." if you're not in a function.
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction ||
window.msIDBTransaction || {READ_WRITE: "readwrite"};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange ||
window.msIDBKeyRange;
// (Mozilla has never prefixed these objects, so we don't need window.mozIDB*)
if (!window.indexedDB)
window.alert(`Your browser doesn't support a stable version of IndexedDB.
Such and such feature will not be available.`);
var db;
var request = indexedDB.open("MyTestDatabase");
request.onerror = function(event) {
console.error('Database error', event.target.errorCode);
};
request.onsuccess = function(event) {
db = event.target.result;
};
// This event is only implemented in recent browsers
request.onupgradeneeded = function(event) {
var db = event.target.result;
// ...
};
const dbName = "the_name";
var request = indexedDB.open(dbName, 2);
request.onerror = function(event) { /* Handle errors. */ };
request.onupgradeneeded = function(event) {
var db = event.target.result;
// Create an objectStore to hold information about our customers.
// We're going to use "ssn" as our key path because it's guaranteed to be unique
var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
// Create an index to search customers by name. We may have duplicates here.
objectStore.createIndex("name", "name", { unique: false });
// Create an index to search customers by email.
objectStore.createIndex("email", "email", { unique: true });
// Use transaction oncomplete to make sure the objectStore creation is
// finished before adding data into it.
objectStore.transaction.oncomplete = function(event) {
// Store values in the newly created objectStore.
var customerObjectStore = db.transaction("customers", "readwrite")
.objectStore("customers");
for (var i in customerData) {
customerObjectStore.add(customerData[i]);
}
};
};
A research report on browser storage.
Offline storage, improved. Wraps IndexedDB, WebSQL, or localStorage using a simple but powerful API.
Open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser.
A Minimalistic Wrapper for IndexedDB.
What should work offline on web?
Apps for warehouses, factories, and other places with unstable web conditions..
Where ever you want user to be able to access some content that he already saw regardless of the connection
Yes, that's (almost) possible
For better user experience, faster apps
with less server load
By Slobodan Stojanovic
CTO of Cloud Horizon and JS Belgrade Meetup organizer. Programmer, mostly JS.