In the previous article, introducing Progressive Web Apps (Offline) – Part 1, we discussed what a typical PWA should look like and also introduced server workers. So far, we have cached the application shell. Our application has implemented offline loading of cached data in index. HTML and latet. HTML pages. They load faster when accessed repeatedly. At the end of the first part of this tutorial, we were able to load latest.html offline, but we were unable to display and retrieve dynamic data while the user was offline. In this study we will:

  • When the user is offlinelatestThe page caches the app’s data
  • usinglocalStorageTo store app data
  • When the user connects to the Internet, the old data of the app is replaced and the new data is updated.

Offline storage

Various storage mechanisms need to be considered when building a PWA:

  • IndexedDB: This is a transactional database system used by clients to store data. IndexedDB allows you to store and retrieve key-indexed objects for high-performance searches of the data stored within them. IndexedDB exposes an asynchronous API to avoid blocking DOM loading. But some studies suggest that in some cases it is blocked. I recommend using some third-party libraries when using IndexedDB, as it can be tedious and complex to manipulate in JavaScript. Third-party modules such as localForage, IDB, and IDB-Keyval are good.

IndexDB compatibility in browsers

  • Cache API: This is the best choice for storing URL address resources. It works well with Service workers.

  • PouchDB: Is an open source JavaScript database for CouchDB. It enables applications to store data locally, go offline, and then come back online when syncing with CouchDB and compatible server applications, keeping users’ data in sync regardless of where they next log in. PouchDB supports all modern browsers, failing to use IndexedDB demoted to WebSQL, For Firefox 29+ (including Firefox OS and Firefox for Android), Chrome 30+, Safari 5+, Internet Explorer 10+, Opera 21+, Android 4.0+, iOS 7.1+ and Windows Phone 8+ are compatible.

  • Web Storage such as localStorage: it is synchronous, prevents DOM loading, uses up to 5MB in the browser, and has a simple API to manipulate key-value pair data.

Browser compatibility table of Web Storage

  • WebSQL: This is a relational database solution for browsers. It is deprecated, therefore, browsers may not support it in the future.

According to Nolan Lawson, the creator of PouchDB, it’s best to ask yourself these questions when using the database:

  • Is the database in memory or on disk? (PouchDB, IndexedDB)?
  • What needs to be stored on disk? What data should be saved when an application shuts down or crashes?
  • What indexes are needed to perform quick queries? Can I use in-memory indexes instead of disk ones?
  • How should I structure my in-memory data relative to my database data? What is my mapping strategy between the two?
  • What are the query requirements of my application? Does presenting a view really need to capture the entire data, or only a fraction of what it needs? Can I lazily load anything?

You can see how to consider database selection for a more complete understanding of topic content.

Without further ado, let’s implement instant loading

In our Web app, we will use localStorage, which I recommend not to use in production due to the limitations I highlighted earlier in this tutorial. The application we are building is very simple, so we use localStorage.

Open your js/ latster.js file, and we update fetchCommits to store data pulled from the Github API in localStorage. The code is as follows:


 function fetchCommits() {
    var url = 'https://api.github.com/repos/unicodeveloper/resources-i-like/commits';

    fetch(url)
    .then(function(fetchResponse){ 
      return fetchResponse.json();
    })
    .then(function(response) {
        console.log("Response from Github", response);

        var commitData = {};

        for (var i = 0; i < posData.length; i++) {
          commitData[posData[i]] = {
            message: response[i].commit.message,
            author: response[i].commit.author.name,
            time: response[i].commit.author.date,
            link: response[i].html_url
          };
        }

        localStorage.setItem('commitData', JSON.stringify(commitData));

        for (var i = 0; i < commitContainer.length; i++) {

          container.querySelector("" + commitContainer[i]).innerHTML = 
          "<h4> Message: " + response[i].commit.message + "</h4>" +
          "<h4> Author: " + response[i].commit.author.name + "</h4>" +
          "<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() +  "</h4>" +
          "<h4>" + "<a href='" + response[i].html_url + "'>Click me to see more! "  + "</h4>";

        }

        app.spinner.setAttribute('hidden'.true); // hide spinner
    })
    .catch(function (error) {
      console.error(error);
    });
};
Copy the code

The code above shows that when the first page loads, the submitted data is stored in localStorage. Now we write another function to render the data in localStorage. The code is as follows:


  // Get the commits Data from the Web Storage
  function fetchCommitsFromLocalStorage(data) {
    var localData = JSON.parse(data);

    app.spinner.setAttribute('hidden'.true); //hide spinner

    for (var i = 0; i < commitContainer.length; i++) {

      container.querySelector("" + commitContainer[i]).innerHTML = 
      "<h4> Message: " + localData[posData[i]].message + "</h4>" +
      "<h4> Author: " + localData[posData[i]].author + "</h4>" +
      "<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() +  "</h4>" +
      "<h4>" + "<a href='" + localData[posData[i]].link + "'>Click me to see more! "  + "</h4>"; }};Copy the code

This code stores the data locally and renders it to dom nodes.

Now we need to know, what condition to call fetchCommits function and fetchCommitsFromLocalStorage function.

The js/latest.js code is as follows


(function() {
  'use strict';

  var app = {
    spinner: document.querySelector('.loader')}; var container = document.querySelector('.container');
  var commitContainer = ['.first'.'.second'.'.third'.'.fourth'.'.fifth'];
  var posData = ['first'.'second'.'third'.'fourth'.'fifth'];

  // Check that localStorage is both supported and available
  function storageAvailable(type) {
    try {
      var storage = window[type],
        x = '__storage_test__';
      storage.setItem(x, x);
      storage.removeItem(x);
      return true;
    }
    catch(e) {
      return false;
    }
  }

  // Get Commit Data from Github API
  function fetchCommits() {
    var url = 'https://api.github.com/repos/unicodeveloper/resources-i-like/commits';

    fetch(url)
    .then(function(fetchResponse){ 
      return fetchResponse.json();
    })
    .then(function(response) {
        console.log("Response from Github", response);

        var commitData = {};

        for (var i = 0; i < posData.length; i++) {
          commitData[posData[i]] = {
            message: response[i].commit.message,
            author: response[i].commit.author.name,
            time: response[i].commit.author.date,
            link: response[i].html_url
          };
        }

        localStorage.setItem('commitData', JSON.stringify(commitData));

        for (var i = 0; i < commitContainer.length; i++) {

          container.querySelector("" + commitContainer[i]).innerHTML = 
          "<h4> Message: " + response[i].commit.message + "</h4>" +
          "<h4> Author: " + response[i].commit.author.name + "</h4>" +
          "<h4> Time committed: " + (new Date(response[i].commit.author.date)).toUTCString() +  "</h4>" +
          "<h4>" + "<a href='" + response[i].html_url + "'>Click me to see more! "  + "</h4>";

        }

        app.spinner.setAttribute('hidden'.true); // hide spinner
      })
      .catch(function (error) {
        console.error(error);
      });
  };

  // Get the commits Data from the Web Storage
  function fetchCommitsFromLocalStorage(data) {
    var localData = JSON.parse(data);

    app.spinner.setAttribute('hidden'.true); //hide spinner

    for (var i = 0; i < commitContainer.length; i++) {

      container.querySelector("" + commitContainer[i]).innerHTML = 
      "<h4> Message: " + localData[posData[i]].message + "</h4>" +
      "<h4> Author: " + localData[posData[i]].author + "</h4>" +
      "<h4> Time committed: " + (new Date(localData[posData[i]].time)).toUTCString() +  "</h4>" +
      "<h4>" + "<a href='" + localData[posData[i]].link + "'>Click me to see more! "  + "</h4>"; }};if (storageAvailable('localStorage')) {
    if (localStorage.getItem('commitData') === null) {
      /* The user is using the app for the first time, or the user has not
       * saved any commit data, so show the user some fake data.
       */
      fetchCommits();
      console.log("Fetch from API");
    } else {
      fetchCommitsFromLocalStorage(localStorage.getItem('commitData'));
      console.log("Fetch from Local Storage"); }}else {
    toast("We can't cache your app data yet..");
  }
})();

Copy the code

In the code snippet above, we are checking to see if the browser supports local storage, and if it does, we continue to check to see if the commit data has been cached. If it’s not cached, we display the requested data on the page and cache the requested data.

Now, refresh the browser again, make sure you do a clean cache, force the refresh, or we won’t see the results of our code changes.

Now go offline and load the latest page. What will happen?

Yaaay!!!!!! It loads data without any problems.

Looking at DevTools, you can see that the data has been cached to localStorage

Look at how fast it loads when the user is offline!!

One more thing

Now we can get the data from the local store immediately. But how do we get the latest data? We need a way to still get new data when users are online.

So easy, let’s add a refresh button that triggers a request to GitHub to get the latest data.

Open the latest.html file and add a refresh button to the

tag

<button id="butRefresh" class="headerButton" aria-label="Refresh"></button>
Copy the code

The

tag after the added button should look like this:


<header>
  <span class="header__icon">
    <svg class="menu__icon no--select" width="24px" height="24px" viewBox="0 0 48 48" fill="#fff">
      <path d="M6 36h36v-4H6v4zm0-10h36v-4H6v4zm0-14v4h36v-4H6z"></path>
    </svg>
  </span>
  <span class="header__title no--select">PWA - Commits</span>
  <button id="butRefresh" class="headerButton" aria-label="Refresh"></button>
</header> 
Copy the code

Finally, let’s attach a click event to the button and add functionality. Open js/latest.js and add the following code:

document.getElementById('butRefresh').addEventListener('click'.function() {
    // Get fresh, updated data from GitHub whenever you are clicked
    toast('Fetching latest data... ');
    fetchCommits();
    console.log("Getting fresh data!!!");
});
Copy the code

Clear the cache and reload. Your latest.html page should now look like this:

Whenever users need the latest data, they can just click the refresh button.

Additional:

Check out the link below

Introduce progressive Web Apps (Offline) – Part 1

The original address

Project code address

Personal Blog Address

If there is any mistake or mistake in translation, please kindly give me your advice. I will be very grateful

Looking forward to the next article: An introduction to progressive Web Apps (Push notifications) – Part 3