Caching pages and templates for offline use

As we stated at the beginning of this chapter, one of the main uses for service workers is to cache page resources for future use. We saw this with our first simple ServiceWorker, but we should set up a more complicated page with more resources. Follow these steps:

  1. Create a brand new ServiceWorker called CacheServiceWorker.js and add the following template code to it. This is what most of the ServiceWorker instances will use:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
// add resources here
]);
}).then(() => {
console.log('we are ready!');
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
)
});
  1. Update our index.html file in order to utilize this new ServiceWorker:
navigator.serviceWorker.register('./CacheServiceWorker.js', { scope : '/'})
.then((reg) => {
console.log('successfully registered worker');
}).catch((err) => {
console.error('there seems to be an issue!', err);
})
  1. Now, let's add some buttons and tables to our page. We will utilize these shortly:
<button id="addRow">Add</button>
<button id="remove">Remove</button>
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Points</th>
</tr>
</thead>
<tbody id="tablebody">
</tbody>
</table>
  1. Add a JavaScript file that will handle all of our interaction with the interactions.js page:
const add = document.querySelector('#addRow');
const remove = document.querySelector('#remove');
const tableBody = document.querySelector('#tablebody');
add.addEventListener('click', (ev) => {
fetch('/add').then((res) => res.json()).then((fin) =>
tableBody.appendChild(fin));
});
remove.addEventListener('click', (ev) => {
while(tableBody.firstChild) {
tableBody.removeChild(tableBody.firstChild);
}
});
  1. Add the JavaScript file to our ServiceWorker as a preload:
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'./interactions.js',
'./main.css'
]);
}).then(() => {
console.log('we are ready!');
})
  1. Add the JavaScript file to the bottom of our index.html file:
<script src="interactions.js" type="text/javascript"></script>

Now, if we load our page, we should see a simple table sitting there with a header row and some buttons. Let's go ahead and add some basic styling to our page to make it a bit easier to see. Add the following to that main.css file that we added when we were working with BaseServiceWorker:

table {
margin: 15px;
border : 1px solid black;
}
th {
border : 1px solid black;
padding : 2px;
}
button {
border : 1px solid black;
padding :5px;
background : #2e2e2e;
color : #cfcfcf;
cursor : pointer;
margin-left : 15px;
margin-top : 15px;
}

This CSS gives us some basic styling to work with. Now, if we hit the Add button, we should see the following message:

The FetchEvent for "http://localhost:3000/add" resulted in a network error response: the promise was rejected.

Since we haven't added any code to actually handle this, let's go ahead and intercept this message inside of our ServiceWorker. Follow these steps to do so:

  1. Add the following dummy code to our fetch event handler for the ServiceWorker:
event.respondWith(
caches.match(event.request).then((response) => {
if( response ) {
return response
} else {
if( event.request.url.includes("/add") ) {
return new Response(new Blob(["Here is some data"],
{ type : 'text/plain'}),
{ status : 200 });
}
fetch(event.request);
}
})
)
  1. Click the Add button. We should see a new error stating that it could not parse the JSON message. Change the Blob data to some JSON:
return new Response(new Blob([JSON.stringify({test : 'example', stuff : 'other'})], { type : 'application/json'}), { status : 200 });
  1. Click the Add button again. We should get something that states that what we just passed to our handler is not of the Node type. Parse the data that we got inside of our Add button's click handler:
fetch('/add').then((res) => res.json()).then((fin) =>  {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${fin.test}</td>
<td>${fin.stuff}</td>
<td>other</td>`;
tableBody.appendChild(tr);
});

Now, if we try to run our code, we will see something interesting: our JavaScript file is still the old code. The ServiceWorker is utilizing the old cache that we had. We could do one of two things here. First, we could just disable the ServiceWorker. Alternatively, we could remove the old cache and replace it with our new one. We will perform the second option. To do this, we will need to add the following code to our ServiceWorker inside the install listener:

event.waitUntil(
caches.delete('v1').then(() => {
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'./interactions.js',
'./main.css'
]);
}).then(() => {
console.log('we are ready!');
});
})
);

Now, we could have the template loaded in on the frontend code, but we're going to mimic a server-side rendered system here instead. There are a couple of applications for this, but the main one that comes to mind is a templating system that we are trying out in development.

Most template systems need to be compiled to their final HTML forms before we can use them. We could set up a watch type system where these templates are reloaded every single time we update them, but that can become tiresome, especially when we only want to focus on the frontend. Another way to do this is to load those templates into our ServiceWorker and let it render them. That way, when we want to make updates, we just have our cache be deleted through the caches.delete method and then reload it.

Let's go ahead and set up a simple example like the preceding one, but instead of the template being created in our frontend code, we will have it in our ServiceWorker. Follow these steps to do so:

  1. Create a template file called row.template and fill it in with the following code:
<td>${id}</td>
<td>${name}</td>
<td>${description}</td>
<td>${points}</td>
  1. Remove the templating code inside of our interactions.js and replace it with the following:
fetch('/add').then((res) => res.text()).then((fin) =>  {
const row = document.createElement('tr');
row.innerHTML = fin;
tableBody.appendChild(row);
});
  1. Let's set up some basic templating code. We will do nothing close to what we did in Chapter 9, Practical Example Building a Static Server. Instead, we will loop through the objects we get passed and fill in the parts of our template where our keys line up in the object:
const renderTemplate = function(template, obj) {
const regex = /${([a-zA-Z0-9]+)}/;
const keys = Object.keys(obj);
let match = null;
while(match = regex.exec(template)) {
const key = match[1];
if( keys.includes(key) ) {
template = template.replace(match[0], obj[key]);
} else {
match = null;
}
}
return template;
}
  1. Change the response to the /add endpoint with the following code:
if( event.request.url.includes('/add') ) {
return fetch('./row.template')
.then((res) => res.text())
.then((template) => {
return new Response(new Blob([renderTemplate(template,
add)],{type : 'text/html'}), {status : 200});
})
} else if( response ) {
return response
} else {
return fetch(event.request);
}

Now, we will grab the template we want from the server (in our case, the row.template file) and fill it with whatever data we have (again, in our case we will use stub data). Now, we have templating inside of our ServiceWorker and can easily set up endpoints to go through this templating system.

This can also be beneficial when we want to personalize the error pages of our site. If we want to have a random image appear and incorporate it in our 404 page, we could have this done in the ServiceWorker instead of hitting this server. We could even do this for an offline state. We would just need to implement the same type of templating that we did here.

With these concepts in mind, it should be easy to see the power we have when intercepting requests and how we can make our web applications work offline. One final technique that we will learn about is storing our requests when we are offline and running them when we go back online. This type of technique can be used for saving or loading files from the browser. Let's take a look.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.251.206