This mini series about “Offline Web Applications” shows you how to build a web application that your users can use while they are offline. I will give a session about this topic at Herbstcampus 2012.

What is still missing?

The dictionary can not be used offline at the moment. We want to change that. We will load the dictionary content from the server into a client side database. Then users of the dictionary can access the data offline. We will not allow editing the dictionary offline.

The client side database we use is IndexedDB, together with the JQuery IndexedDB plugin.

The indexeddb plugin

We have to add the JQuery IndexedDB plugin to our HTML and offline manifest:

dictionary.html

<head>
    <!-- ... -->
    <script src="jquery-1.8.0.min.js"></script>
    <script src="jquery.indexeddb.js"></script>
    <!-- ... -->
</head>

offline.manifest

CACHE MANIFEST
# Offline cache manifest for the dictionary application
# version 2

CACHE:
dictionary.html
style.css
jquery-1.8.0.min.js
jquery.indexeddb.js
dictionary.js

NETWORK:
api/listEntries
api/newEntry

The complete application

We have to change the HTML page slightly: When the document is loaded, we have to do more than just loading the dictionary content. So we change this function call to “initialize”. Also, the function that submits the data needs the values of the two input fields, so it can store them to the IndexedDB.

dictionary.html

<head>
    <!-- ... -->
		
    <script>
        $(document).ready(function() {
            initializeDictionaryApplication();
            $('#submitButton').click(function() {
                submitDictionaryEntry($('#addForm'),
                $('input[name="german"]').val(), 
                $('input[name="english"]').val());
                return false;
            });
        });
    </script></strong>
</head>

The JavaScript code has to be changed significantly. To initialize the application, we first have to initialize the offline database. This is an asynchronous invocation, but we can add a callback function which is notified when the initialization is done. Then we can load the server content.

dictionary.js

initializeDictionaryApplication = function() {
    initializeOfflineDatabase().done(function() {
        loadDictionaryContent();
    });
};

The schema that is required to initialize the database is a mapping of version numbers to functions. Each function describes how the schema has to be changed to get from the previous version to the current. This initialization routine creates a database called “dictionary”. The database contains a single object store called “Words”, where we will store “Word” objects.

function Word(word, translation) {
    this.word = word;
    this.translation = translation;
}

initializeOfflineDatabase = function() {
    return $.indexedDB("dictionary", {
        "schema" : {
            "1" : function(transaction) {
                var dictionary = transaction.createObjectStore("words", {
                    "keyPath" : "word"
                });
                dictionary.createIndex("translation");
            }
        }
    });
};

Loading the database works similar as before, but instead of showing the data directly we save it to the database. Then we show the database content. When an error occurs (i.e. when the user is offline) we just show the database content, which still contains all the words that were loaded the last time the user was online.

loadDictionaryContent = function() {
    $.ajax({
        url:'api/listEntries'
    }).done(function(data) {
        $.indexedDB("dictionary").objectStore("words").clear();

        var dataArray = JSON.parse(data);
        var words = $.indexedDB("dictionary").objectStore("words");
        
        $.each(dataArray, function(index, value) {
            words.add(new Word(value.german, value.english));
        });
        
        showDatabaseEntries();
    }).error(function() {
        showDatabaseContent();
    });
};

Showing the database content just iterates over all entries in the object store and creates the corresponding HTML.

showDatabaseEntries = function() {
    $("#wordsList").empty();
    $.indexedDB("dictionary").objectStore("words").each(function(item) {
        $("#wordsList").append('<div class="left">' + item.value.word + '</div>');
        $("#wordsList").append('<div class="right">' + item.value.translation + '</div>');
        $("#wordsList").append('<div class="clear"></div>');
    });
};

When a new word is submitted, it is not only sent to the server, we also store it in the database. Note that for submitting words, we still need an online connection. We could make this feature available offline too, but then we would need a synchronization mechanism. This is left as an exercise to the reader :)

submitDictionaryEntry = function(form, german, translation) {
    $.indexedDB("dictionary").objectStore("words")
            .add(new Word(german, translation))
            .done(function(result, event) {
        sendEntryToServer(form.serialize());
        showDatabaseEntries();
    });
};

sendEntryToServer = function(formContent) {
    $.ajax({
        url:'api/newEntry',
        type: 'GET',
        data: formContent
    });
};

Conclusion

Storing data offline has become really easy for web applications, but it is not yet supported by all browsers. In the not-so-far future this will be an interesting alternative to native apps.