Let's make Drupal 8 available offline using AppCache

Published by Rok
on 18 October 2016

Mobile apps are known to handle offline world very good with their native apps capability, which can cache basically all the assets whereas web applications are falling behind, losing this battle. A lot of us cannot afford to create a dedicated mobile app. Therefore, we need to find some solution with what we already have.

That solution is Application Cache or AppCache in short. Of course, as any other software, it is not perfect and it has its gotchas. Moreover, for some time now it has been marked as deprecated, but basically no other real alternative exists. Actually, there is an alternative, which will eventually take AppCache’s place, but unfortunately it is not yet supported in all major browsers. It is called ServiceWorker. It has some neat features such as push notifications, but more about it in one of our next posts.

AppCache allows you, as a developer, to select which files a browser should cache, so they are available for the offline users. This means that your web page will work even if a user loses internet connection and reloads the web page.

 

AppCache2

Cache manifest file

Cache manifest file is a simple text file, which serves as a config file, where all resources, which the browser should cache for offline use, are listed.

To enable AppCache on any site, you need to include mentioned manifest file as an attribute to your HTML tag like this:

<html manifest="manifest-example.appcache">

Of course this attribute must be included on every page, which you want to be cached. Pages in your manifest file will also be cached, even if they do not have manifest attribute.

Please note that AppCache does not distinguish between GET variables and URL path, which means that “/some-url/?foo” and “/some-url/?bar” are from AppCache’s stand of view two different resources and will both be cached. So, it is best to put only one resource for AppCache to cache.

There’s a neat feature in Chrome, which allows you to manage browser’s cache, by visiting chrome://appcache-internals/.

Please note that manifest file must be served as text/cache-manifest mime-type, which means that you might want to update your Apache2 or Nginx to return correct mime for .appcache extensions.

For Apache2 simply add following line to your config:

AddType text/cache-manifest .appcache

Structure of manifest file

Structure of the file is quite simple. Here is one example, which I shall explain below.

CACHE MANIFEST
# 2010-06-18:v3

# Explicitly cached entries
index.html
css/style.css

# offline.html will be displayed if the user is offline
FALLBACK:
/ /offline.html

# All other resources (e.g. sites) require the user to be online. 
NETWORK:
*

# Additional resources to cache
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

First line CACHE MANIFEST is mandatory.

As you can see, # are comments. If you want to force the browser to re-cache the files, you also need to make a change in your manifest file. You can do this by changing a comment, hence the second line, which is actually some sort of cache version.

Entries after the mandatory line are URLs which browser will cache.

FALLBACK are basically some sort of redirects. Following line:

/ /offline.html

will redirect user to /offline.html when he will try to reach / while offline.

With NETWORK directive you specify, which URLs are white-listed resources that require a connection to the server. All this URLs bypass the cache, even if the user is offline. Usually you need to put “*”, meaning all other URLs will require a network.

At the end you can see CACHE directive. Those are just more resources, which we want to cache.

NOTE: cache can be updated in one of the following ways

  1. User clears the cache manually from the browser
  2. Manifest file is altered. If one of the resources, which is listed in manifest, is changed, the browser will NOT re-cache it. Manifest itself must be changed to force a re-caching procedure.

Drupal 8

When reading this post, you must have been wondering, why does the title mention Drupal and all this guy is writing is some technical gibberish. Well… It is good to know how things work under the hood sometimes, even though you probably will never need to use this knowledge when using Drupal module for AppCache. And to be fair, I kind of told you to skip the first part haven’t I?

So, let us move on to Drupal. But first let me explain, how AppCache for Drupal is designed:

  1. Manifest attribute is added to pages, which are completely separated from the online pages, even if they serve the same content. This means you need to make two versions of it.
  2. Those offline pages have to have different route than the original, for example node/1 becomes offline/node/1 and should use different template.
  3. Manifest file should also contain assets, such as javascript, styles, logos and images

Let’s download and install the offline_app module for Drupal 8 using Drupal Console, which implements the use of AppCache by this two commands:

# drupal module:download offline_app
# drupal module:install offline_app

Now, let’s make some content available offline, shall we?

I have prepared a clean Drupal 8 installation for this purpose, so let’s go ahead and generate some random Articles by using Devel module by visiting following URL (do not forget to enable Devel generate as well):

/admin/config/development/generate/content

If you do not have Devel module, you can get it and enable it by using Drupal Console like this:

drupal module:download devel
drupal module:install devel
drupal module:install devel_generate

Since we want to show our Articles, we make a simple Page View and name it “articles”. We select to display Teaser mode and Path /articles. The View should now look like this:

Articles in AppCache

If you save the view and visit /articles, you should see all of your articles.

So, if we want this view to be available offline, we need to add new Display to this view and select offline. You can see that I have already done that on the previous image.

Once you do that, go ahead and edit Content view mode so it reads “Offline teaser”. Rest of the settings can be left alone, unless you want to add some custom filters, sorting or pagers. The view should look like this:

Articles in AppCache 2

NOTE: if you want to display Offline teaser on one display and Teaser on another, you have to first click on Content and select “For: This … (override)”. Otherwise you will be changing this settings on both displays at once.

Offline teaser

Since we decided to show Content and not fields in View, we need to go to node display page, where we decide, which fields to render. So, go ahead and visit the following page:

/admin/structure/types/manage/article/display

At the moment you probably have just 3 displays, Default, RSS and Teaser. On the bottom of this page there is a “Custom display settings” text, which you should click and select “Offline teaser” and click save.

Offline teaser 2

Now the “Offline teaser” display should be visible for us to click and edit. Now we can:

  • select Image format to Offline image and medium size
  • Body format to Trimmed to 600 chars and
  • Disable comments

Save it!

Manage display

Now go to AppCache’s configuration page, which is located here:

/admin/config/services/offline-app

There are quite few settings for you to check. Let’s cover the basics

  • Manifest tab has Pages, Fallback and Network. Fallback and Network are already filled. If you want to know more, please read the beginning of this post. Pages section contain URLs, which you want to explicitly cache.
  • Content tab has Pages. This is where we will be entering our view and some nodes. Homepage and Offline messages are self explanatory. Menu is basically a menu, which you generate for your offline app. More about it later on. Images are links to all the images.
  • Assets are CSS’s and JS’s, which you want to include to your offline page.
  • And then there are Settings and Homescreen, which are not really important at the moment.

First, we go to Content tab and add our new view by writing:

articles/view:articles:offline_1

Syntax is written below the textarea and it says that you should use alias/view:name:display, when referencing a view.

Syntax

For the sake of testing let’s add one more node page. My first Article in the view, which we created earlier, has a NID of 26. So to add it, we enter it below the articles view, which we entered earlier, like this:

this-is-nid-26/node:26

Last thing we need to set is the Menu. This menu will be shown, when a user visits /offline of our page. Therefore, we will add two items. First one will be home, which points to home page of offline page. Second one is a link to our articles view. So, this is what we need to put in:

appcache-fallback/Home
articles/Articles

Now, we save the configuration and visit our offline section of the page by going here:

/offline

This is what we should see:

Offline view

This welcome text can of course be adjusted to your needs.

Browser should now sync all the files in its internal cache, meanwhile you can take a look at your /manifest.appcache file which should look like this:

CACHE MANIFEST
# Timestamp: 1467983502
/offline/articles
/offline/this-is-nid-26
FALLBACK:
/ /offline/appcache-fallback
NETWORK:
*

Let’s go offline! Turn off your network connection, if you are using containers of Vagrant, just shut them down. I used docker, so what I do is:

docker stop d8

While offline, go to your website, to the root of your website (/). You should see a welcome screen like this one:

Offline view 2

This happens, because we have a FALLBACK entry in our manifest file, which tells our browser to fetch /offline/appcache-fallback if it cannot fetch (your drupal’s root page).

Now, go to the Articles. You should see all the articles. But wait, there are no images? Well, if you take a look at your manifest file again, you will see that there are no images listed and since we use images in our view, we need to also explicitly put images in the manifest.

So, let’s turn our internet connection back on, or just start your container or Vagrant box and head to AppCache configuration page. Under the Content tab click on Images, click the checkbox and then click the link below the textarea, which says “Click here to update the list of images”. Page should refresh. Go ahead and click on Images again. This time there should be links to all of the images, which our view is using to render articles. Save the configuration and then head to /manifest.appcache and refresh it few times. Those links to images should appear. If they don’t, go back to AppCache settings, to Images and copy all the text and paste it in Manifest tab, Pages section and click Save. If you check your /manifest.appcache, those links to images should appear (refresh few times). I think this is a bug, since the checkbox in Images clearly states that those images will be added to the manifest, but they are not. Anyway, as I said, there is a workaround. You can just put them in directly.

So, if you go offline and revisit the view, you should have all the images visible even though you are not connected.

Images offline

You might be seeing slightly uglier output. This is because I have added aggregated CSS files to Assets like this:

Assets offline

Since we added one article node to offline as well, you should see, if you hover over the titles from the offline view, that the first one has a link to /offline/this-is-nid-26 whereas others point to /. So, AppCache module knows that the first article is also available offline. This is why it makes a link to it. If you click on it, full node content should pop up.

AppCache Module not the final solution

This module of course is not a perfect solution, but you can achieve to publish your content offline in short period of time. There are still some bugs and some features missing, such as, automatically add nodes to the manifest, which are included in the view. One other thing to note is, if any of the resources is not available, when the browser caches resources, cache will not be updated. This is not due to this module, but nature of AppCache itself.