Convert Django Website to a Progressive Web App [Part 2/2]

In the last part, we have cached our website’s homepage layout using Cache API and service worker. But we still don’t see any feeds on our page. Don’t worry! We are going to use IndexedDB Promised API in this part to store the feeds/articles at client side and make it available offline to the user.

Prerequisites:

  1. IndexedDB Promised API (Learn more about it here)
  2. Part 1 of this article

In order to use the IndexedDB Promised API, we need to add idb.js file in our project. From GitHub copy this source code and save the file as idb.js inside /static/js folder. Also create an empty JavaScript file called idbop.js and save it in the same/static/js folder. Your project directory should look like this:

How are we going to proceed?

  1. Add JavaScript files in our html page.
  2. Fetch news feeds from server using JavaScript fetch API.
  3. Store fetched data in IndexedDB.
  4. Retrieve stored data from IndexedDB and display on base.html page.

Step 1: Add JavaScript files in our html page.

Open base.html file which we have created in first part. add following two lines just before <div class=”footer bg-dark”> .

<script type="text/javascript" src="{% static 'js/idb.js' %}"></script>
<script type="text/javascript" src="{% static 'js/idbop.js' %}"></script>

Remember, idb.js file is used for IndexedDB Promised API. We are not going to change anything in idb.js file. All IndexedDB operations will be carried out by idbop.js file.

Your html file should look something like this:

<!-- Above part is same as previous base.html file -->
<script type="text/javascript" src="{% static 'js/idb.js' %}"></script>
<script type="text/javascript" src="{% static 'js/idbop.js' %}"></script>
</div>
<div class="footer bg-dark">
<p>&copy Feeds 2018</p>
<a class="alink" href="#">Privacy policy</a>&nbsp
<a class="alink" href="#">Contact us</a>
</div>
</div>
</body>
</html>

Step 2: Fetch news feeds from server using JavaScript fetch API.

To store the news feeds in IndexedDB, we will need to fetch all the news feeds from server. This can be done by JavaScript fetch API. fetch API will make a HTTP request to server and it will return a HTTP response using JavaScript Promises. We will request server to get news feed data in JSON format. In order to do so, we have to make a new function in views.py file which will return JSON response containing all news feed data at client side for storing in IndexedDB.

In views.py file we will make a new function getdata() .

def getdata(request):
results=feed.objects.all()
jsondata = serializers.serialize('json',results)
return HttpResponse(jsondata)

The above function will retrieve all the news feeds from MySQL table and will return data in JSONformat as Http response.

The updated views.py file should look like this:

views.py

JavaScript Fetch API will return this response at client side by making request to the URL http://127.0.0.1:8000/getdata . The first thing that we will do in idbop.js is to fetch JSON response from this URL.

fetch('http://127.0.0.1:8000/getdata').then(function(response){
return response.json();
}).then(function(jsondata){
console.log(jsondata);
});

The above fetch promise will return JSON array. Open console in chrome and you will see JSON array like this:

JSON array fetched from a server.

The JSON array contains 3 key-value pairs.

  1. model: The name of the table from which the data is retrieved.
  2. pk: Primary key for one record in that table.
  3. fields: Columns from that table in JSON array.

The value of fields key is nested JSON array which contains column name as a key and data as a value. If we expand above nested JSON more we will get a clear picture of it:

Now you can see table columns author , body and title as a key in JSON array of fields . If you are able to see this columns, you have successfully fetched data from server!

Step 3: Store fetched data in IndexedDB.

The data which is fetched by JavaScript Promised API needs to be stored on client side database. We will now open new database connection to IndexedDB to store all these data in idbop.js file. We will use pk field of JSON as a key in IndexedDB.

var dbPromise = idb.open('feeds-db', 1, function(upgradeDb) {
upgradeDb.createObjectStore('feeds',{keyPath:'pk'});
});

Open developers tools in Chrome and head over to the application tab. You will see the new database in IndexedDB called feeds-db .

Version may differ in your case.

We will write an IndexedDB transaction inside fetch promise, which will store the fetched data in feeds-db .

fetch('http://127.0.0.1:8000/getdata').then(function(response){
return response.json();
}).then(function(jsondata){
dbPromise.then(function(db){
var tx = db.transaction('feeds', 'readwrite');
var feedsStore = tx.objectStore('feeds');
for(var key in jsondata){
if (jsondata.hasOwnProperty(key)) {
feedsStore.put(jsondata[key]);
}
}
});
});

Data will get stored inside the objectStore . Think objectStore as a table in MySQL. Here we will create feeds objectStore to store our news feeds. put() method will insert each JSON array as a one record inside feeds .

feeds objectStore

Step 4: Retrieve stored data from IndexedDB and display on base.html page.

Our data is now available in IndexedDB database. All we have to do is display this data to user, so even if you don’t have an active internet connection you can read news. We will be displaying the news on base.html file because that’s the file which is cached by our service worker. We will create an empty div with id offlinedata . idbop.js will display news inside this div . Updated base.html file should look like this:

base.html:

Now, we will write new idb transaction for read-only purpose.

var post="";
dbPromise.then(function(db){
var tx = db.transaction('feeds', 'readonly');
var feedsStore = tx.objectStore('feeds');
return feedsStore.openCursor();
}).then(function logItems(cursor) {
if (!cursor) {
document.getElementById('offlinedata').innerHTML=post;
return;
}
for (var field in cursor.value) {
if(field=='fields'){
feedsData=cursor.value[field];
for(var key in feedsData){
if(key =='title'){
var title = '<h3>'+feedsData[key]+'</h3>';
}
if(key =='author'){
var author = feedsData[key];
}
if(key == 'body'){
var body = '<p>'+feedsData[key]+'</p>';
}
}
post=post+'<br>'+title+'<br>'+author+'<br>'+body+'<br>';
}
}
return cursor.continue().then(logItems);
});

Here, we will use idb cursor to iterate over each record inside feeds object store. We will use inner for loop to iterate over nested JSON array inside fields key. This inner for loop will help us to get values of title ,author and body of the news. We will add html formatted string in post variable. Once we are done iterating over all records, if(!cursor) condition will get satisfied and the html data post will get written on base.html page using innerHTML property. The final idbop.js file should look like this:

idbop.js

Now go to the application tab in chrome, open service workers and tick the offline mode. Refresh the page and see the beauty of the Progressive web app!

Feeds in offline mode!

Congratulations!! You have successfully converted Django website to a progressive web app. Isn’t it amazing? Also you don’t have to worry about updates of feeds. Once you go online and refresh the page again, the fetch promise will fetch the latest news for you and it will get displayed on your screen.

This is just a demo application for PWA purpose. You can go ahead and add the functionalities like posting your news, deleting the old news, register, login and much more!

Thank you for reading! In case you are facing any issues, checkout GitHub link of the project below.

GitHub link of the project: Link

Part 1 of this article: Link

Resources:

  1. Service Worker
  2. Cache API
  3. Fetch API
  4. Article on IndexedDB Promised API (GitHub Link)

--

--