2

Javascript , offline webapp , serviceworker ,

Javascript , offline webapp , serviceworker ,

Building a web application is great. But building a web application which can run both offline and online seamlessly is awesome.You leverage Javascript, HTML, CSS… skills to build a custom application which works on your browser with or without internet connection. A few weeks ago I dove deeper into offline web applications with the courses offered by Udacity. After acquiring the basic skills, I built an offline web application. This application is a currency converter. It is very easy to implement. And we will be talking about how to implement it. Through out this post, the code snippets will be written in ES6 syntax or above.

What We Will Build

The offline web app we will build is a currency converter. A very simple currency converter. It will take two currencies, one to convert from and the other to convert to. The amount will be input and the conversion process will take place. After completion, results will be displayed properly.

To build the web app, this API will be used : Free Currency Converter API. Requests will be sent to this API and the results will be processed with Javascript. We will focus mostly on the application logic, and not on the layout, I used SemanticUI for a little bit of styling.

Here is the source code for the demo application we will build. The complete offline web app is hosted here, in case you will like to try it before building yours.

Building an Offline Web App

First, you need to know, this tutorial is for beginners. Since our main Objective is to build an offline web app, we will focus on the most important parts of making a web application work offline. Focusing on these parts will help absorb the most important parts of the process of building offline web apps and ignoring the rest. Remember, the source code is on Github and the link is mentioned above. And, the offline web app is also hosted on Github and available at the link mentioned above. So here are the topics under which the application will be built.

Service Workers

Cache API

IndexDB

The Service Worker

Let’s talk about the role which service workers play in this offline web app. First, a recap of what a service worker is. A service worker is the backbone of offline web apps. It is a script which is able to intercept web requests from a web app and provide custom responses.

Here, is how we will use the service worker in this web app.

First, we register the service worker. The registration is done when the service worker’s install event fires. When registering it, we provide a set of URLs which are going to be called and the response will be saved by the cache. This is done in the sw.js file. On the first run of the app, there should be internet connection for the responses to be loaded in the cache.

self.addEventListener('install', (event) => { event.waitUntil( //I cache the currencies caches.open(cacheName).then((cache) => { return cache.addAll([ './', './js/main/idbManager.js', './js/idb.js', './styles/semantic.min.css', './js/main/converter.js', './js/main/main.js', './js/main/serviceWorker.js', 'https://free.currencyconverterapi.com/api/v5/currencies', ]); }) ) }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 self . addEventListener ( 'install' , ( event ) = > { event . waitUntil ( //I cache the currencies caches . open ( cacheName ) . then ( ( cache ) = > { return cache . addAll ( [ './' , './js/main/idbManager.js' , './js/idb.js' , './styles/semantic.min.css' , './js/main/converter.js' , './js/main/main.js' , './js/main/serviceWorker.js' , 'https://free.currencyconverterapi.com/api/v5/currencies' , ] ) ; } ) ) } ) ;

The service worker will then listen to fetch events (When any web request is made by the app). When this event fires, it will first compare the request URL with the content of the cache. If there is a match, it responds with what was saved in the cache. Else, it performs the intended web request and responds with what is gotten from the internet. With this, we have implemented half of the offline functionality. Here is the code for this step.

self.addEventListener('fetch', (event) => { let url = event.request.url; event.respondWith( //Respond with the cached response or if no response is saved, //respond with result from the network. caches.match(event.request).then((response) => { if(response) return response; return fetch(event.request); }) ) }); 1 2 3 4 5 6 7 8 9 10 11 12 13 self . addEventListener ( 'fetch' , ( event ) = > { let url = event . request . url ; event . respondWith ( //Respond with the cached response or if no response is saved, //respond with result from the network. caches . match ( event . request ) . then ( ( response ) = > { if ( response ) return response ; return fetch ( event . request ) ; } ) ) } ) ;

The Cache

The cache has already been used above. The cache’s role is to cache responses to web requests made by the web app. It works with the service worker to provide an offline experience. The code above demonstrates how the cache saves requests and provides responses.

Inside the converter.js file, every logic for currency conversion is written. The cache is made use of here.

As an offline web app, the currencies which are available for conversion should be loaded without internet. This is done with the cache’s help. A fetch to the currency converter API is made and the service worker’s fetch event is fired, and the content of the cache is sent. Here is the method to fetch the currencies.

getAllCurrencies(callBack) { fetch("https://free.currencyconverterapi.com/api/v5/currencies") .then(response => callBack(null, response)) .catch(error => callBack(error, null)); } 1 2 3 4 5 6 getAllCurrencies ( callBack ) { fetch ( "https://free.currencyconverterapi.com/api/v5/currencies" ) . then ( response = > callBack ( null , response ) ) . catch ( error = > callBack ( error , null ) ) ; }

IndexDB

IndexDB is simply used to save data. It is a kind of NoSQL database. It will play an important role in making our currency converter web app really offline.

When the user selects the currencies he/she will want to convert, the conversion should be done both offline or online. Here is how we will accomplish this. Working with IndexDB is alot easier with this library which I use. All the code to query IDB is found in the file named idbmanager.js.

When a conversion is done for the first time, the conversion rate is queried from the API and saved in IndexDB. If it is not the first time for this conversion, its rate is gotten from IDB directly and used to calculate the conversion. With this approach of getting conversion rates saved locally first, the web app will be able to perform conversions offline. Provided that the conversion was performed online before. Here is the code to perform these conversions.

//Converts the currency convertCurrency(amount, fromCurrency, toCurrency, callBack) { fromCurrency = encodeURIComponent(fromCurrency); toCurrency = encodeURIComponent(toCurrency); const query = fromCurrency + '_' + toCurrency; //we build the URL const url = `https://free.currencyconverterapi.com/api/v5/convert?q=${query}&compact=ultra`; //Inquire IDB for the objec'ts query this._idbManager.getQueryValueByID(query, (error, value) => { if(error){ callBack(error); return; } //if the value was not found in idb, query the internet if(!value){ fetch(url) .catch(error => callBack(error)) .then(results => { //Invoke's the call back method of the upper layer using this class after //converting the result to json. results.json().then(jsonData => { //save the value and the query in idb first this._idbManager.saveQueryInDatabase(query, jsonData[query]); let total = jsonData[query] * amount; callBack(null, (Math.round(total * 100) / 100)); }); }); } //If the value was found in idb else{ //get the value of the query let val = value['value']; let total = val * amount; callBack(null, (Math.round(total * 100) / 100)); } //value.value }) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 //Converts the currency convertCurrency ( amount , fromCurrency , toCurrency , callBack ) { fromCurrency = encodeURIComponent ( fromCurrency ) ; toCurrency = encodeURIComponent ( toCurrency ) ; const query = fromCurrency + '_' + toCurrency ; //we build the URL const url = ` https : //free.currencyconverterapi.com/api/v5/convert?q=${query}&compact=ultra`; //Inquire IDB for the objec'ts query this . _idbManager . getQueryValueByID ( query , ( error , value ) = > { if ( error ) { callBack ( error ) ; return ; } //if the value was not found in idb, query the internet if ( ! value ) { fetch ( url ) . catch ( error = > callBack ( error ) ) . then ( results = > { //Invoke's the call back method of the upper layer using this class after //converting the result to json. results . json ( ) . then ( jsonData = > { //save the value and the query in idb first this . _idbManager . saveQueryInDatabase ( query , jsonData [ query ] ) ; let total = jsonData [ query ] * amount ; callBack ( null , ( Math . round ( total * 100 ) / 100 ) ) ; } ) ; } ) ; } //If the value was found in idb else { //get the value of the query let val = value [ 'value' ] ; let total = val * amount ; callBack ( null , ( Math . round ( total * 100 ) / 100 ) ) ; } //value.value } ) }

Here is the code to perform database access with IndexDB.

export const databaseName = 'currency-converter-007damiendoumer-1'; export default class IDBManager{ constructor(){ this._idbPromise = this.setupDatabase(); } setupDatabase(){ // If the browser doesn't support service worker, // we don't care about having a database if (!navigator.serviceWorker) { return Promise.resolve(); } return idb.open(databaseName, 1, upgrade => { let store = upgrade.createObjectStore(databaseName, { keyPath: 'query' }); return store; }); } //Save a query in index db saveQueryInDatabase(query, value){ this._idbPromise.then((db) => { if(!db) return; let transaction = db.transaction(databaseName, 'readwrite'); let store = transaction.objectStore(databaseName); store.put({value:value, query:query}); }) } //get value from database getQueryValueByID(query, callBack){ //Our ID is query in idb this._idbPromise.then(db => { return db.transaction(databaseName).objectStore(databaseName) .get(query); }).then(object => callBack(null, object)) .catch(error => callBack(error, null)); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 export const databaseName = 'currency-converter-007damiendoumer-1' ; export default class IDBManager { constructor ( ) { this . _idbPromise = this . setupDatabase ( ) ; } setupDatabase ( ) { // If the browser doesn't support service worker, // we don't care about having a database if ( ! navigator . serviceWorker ) { return Promise . resolve ( ) ; } return idb . open ( databaseName , 1 , upgrade = > { let store = upgrade . createObjectStore ( databaseName , { keyPath : 'query' } ) ; return store ; } ) ; } //Save a query in index db saveQueryInDatabase ( query , value ) { this . _idbPromise . then ( ( db ) = > { if ( ! db ) return ; let transaction = db . transaction ( databaseName , 'readwrite' ) ; let store = transaction . objectStore ( databaseName ) ; store . put ( { value : value , query : query } ) ; } ) } //get value from database getQueryValueByID ( query , callBack ) { //Our ID is query in idb this . _idbPromise . then ( db = > { return db . transaction ( databaseName ) . objectStore ( databaseName ) . get ( query ) ; } ) . then ( object = > callBack ( null , object ) ) . catch ( error = > callBack ( error , null ) ) ; } }

Conclusion

We have completed our offline currency converter web application. The part which was emphasized on in this post is the application logic with Javascript. The full and well commented source code is available on Github here. You can play with the application, which I hosted on Github pages here.

If this post was useful to you, please share it and follow me on Twitter, Github or like my Facebook page.

Like this: Like Loading...

Like this: Like Loading...

Follow me on social media and stay updatedFollow me on social media and stay updated