Persisting data in your iBooks Author widgets with localStorage

localStorage for iBooksAuthorHey there, it’s Tom here. I’m one of the developers at Bookry and if you’ve been watching any of our videos you’ve probably heard me talking about some of our great widgets already! I love being able to give authors the tools to write books that really stand out; this is one of the reasons we started building widgets. While talking to people it’s come to my attention that it’s not just authors that are interested in widgets. Professional and indie programmers are too. I want to share some of my Bookry know-how with you so that you can also build some great widgets.

In this, my first blog post, I want to introduce you to localStorage and how it works in iBooks Author. LocalStorage provides your widget with the means to persist data across sessions. So for example if you’ve ever played with our Sketchpad widget you might have noticed that when you leave the widget and return to it later your drawing is still there. LocalStorage is the mechanism behind this.

Getting to grips with localStorage

There are loads of guides on the internet explaining how to use localStorage but for those developers new to it I just want to briefly run through how it works. Depending on which browser you’re using you may or may not be able to use localStorage; most modern browsers do support it. You can test for and access the localStorage object like so:

if(window.localStorage) {
    alert(window.localStorage);
}

Brilliant! So now that we know we’ve got it and we can do something with it. Lets store some data in it, it works just like an object, so set a value for a key (note we can only set Strings. You may want to use JSON encoding for more complex cases):

localStorage['name'] = 'Tom';

we can now access this in exactly the same way we would an object:

alert(localStorage['name']);

These are the bare bone basics you will need to know about. So why not try it out? Fire up your favourite editor and give it a go. Place it into a widget, exit the widget and go back in to it. Check to see if your data is still there (It will be!).

There are also methods you can use on the localStorage object that get, fetch and delete data. These maybe useful for you while you’re developing widgets

localStorage.setItem('name', 'Tom'); // Sets the item just as above
localStorage.getItem('name'); // Gets the items just as above
localStorage.removeItem('name'); // Removes the item for this given key
localStorage.clear(); // Removes all data for all keys

Using localStorage in iBooks Author

Now that we know the basics of localStorage lets take a look at it in iBooks and iBooks Author. The environment that you will be working with here is slightly different to that of a normal web browser. Firstly when you close your widget using the cross in the top left corner it doesn’t always close. If you’re using a newer iPad it just gets hidden in the background. The way to close it properly is to navigate two pages away and then navigate back again. This will ensure that when you re-open it you will be opening it from scratch.

There is a second very important thing to know about localStorage in iBooks. Normally on a web browser it is unique to each domain. So if you consider the following two domains…

  1. http://bookry.com
  2. http://classwidgets.com
… neither will be able to read or modify the localStorage object from the other. Each domain has its own unique localStorage object.
With iBooks this changes slightly. Instead each book has it’s own localStorage object. This means that if you have three widgets in one book, they will all share the same localStorage object. So what does this mean when we’re developing widgets?
  • Clearing your localStorage in any widget is generally a big no no. Doing so will wipe any data in any widget in that book.
  • All your widgets share the same 5MB limit, meaning you can only store up to 5MB of localStorage data in a single book.
  • You need to make sure you namespace the data in your localStorage object properly so that you do not end up with conflicts.

So let’s take a look at how we might do this. We could for example wrap our localStorage object up in a set of functions to ensure we don’t interfere with any other widget like this:

var widgetType = 'test'; // Each widget needs a unique type

function set(key, value) {
    localStorage[widgetType + ':' + key] = value;
}
function get(key) {
    return localStorage[widgetType + ':' + key];
}
function remove(key) {
    delete localStorage[widgetType + ':' + key];
}

The first thing we need to do is give our widget a type or identifier. This must be unique for each widget. This allows us to namespace the data we are going to save in the localStorage object. Then by creating some convenience functions around getting, setting and removing items we can make sure that we don’t forget that about namespacing our keys. Finally we may want to implement a clear function. This is a little bit more complicated but no more than a few lines of code, as you can see below:

function clear() {
    for (var k in localStorage) {
        if (k.indexOf(widgetType + ':') === 0) {
            delete localStorage[k];
        }
    }
}

Provided you ensure all the widgets in your book conform to this then you wont have any problems.

Right so lets finish off with a more complete example. Below is some sample HTML and JavaScript that will show you the localStorage variables and also store a new one every time it’s loaded. If you want to try it out as more than one widget, simply change the widgetType variable.

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body>
    <p><strong>Before:</strong></p>
    <div id="before"></div>
    <p><strong>After:</strong></p>
    <div id="after"></div>
    <script type="text/javascript">

        // Create our getters and setters
        var widgetType = 'test';
        function get(key) {
            return localStorage[widgetType + ':' + key];
        }
        function set(key, value) {
            localStorage[widgetType + ':' + key] = value;
        }

        // Add some information to the DOM when we start
        document.getElementById('before').innerHTML = get('random');
        var randomNumber = Math.random();
        document.getElementById('after').innerHTML = randomNumber;
        set('random', randomNumber.toString());

    </script>
</body>
</html>

When you run this you will see that each time you reload the widget it remembers the last random number stored. Of course you can now use this functionality to remember anything from drawings to calculations.

Have fun building some great widgets that can store data with the book!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>