Chapter 6. Making It Easier with Libraries

“Use the Library, Luke...”

OK, so that may not be exactly how the quote goes, but consider this. Client-side storage is a useful feature of modern browsers. Because it is useful, friendly developers have created libraries to help make using client-side storage even easier. In some cases, these libraries make the APIs easier to use. In some cases, they add features that the native API doesn’t even support. As you can imagine, there are quite a few of these libraries available to you, but in this chapter we’ll look at three in particular: Lockr, Dexie, and localForage.

Working with Lockr

Our first library is Lockr, a wrapper for Web Storage (see Figure 6-1). Right away you may be wondering why in the heck anyone would need to make Web Storage simpler, but stick with me and you’ll see why in a moment. Lockr provides a Redis-like API for Web Storage, but don’t worry if you’ve never heard of Redis. It is a very small library (2.5 KB), and like everything we’ll be covering in this chapter it is free and open source. The Lockr home page may be found here: https://github.com/tsironis/lockr.

Lockr’s home on GitHub
Figure 6-1. Lockr’s home on GitHub

You can grab the source from GitHub or install via Bower: bower install lockr. Once you have the library downloaded, simply include it in your code like any other JavaScript library.

Using Lockr is relatively simple. So, for example, this will set a value:

Lockr.set("name", "Raymond");

And this will get a value:

Lockr.get("name");

So why bother? Well, let’s consider two interesting examples. First, take a look at Example 6-1.

Example 6-1. lockr/test1.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="lockr.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {

    Lockr.set("name", "Ray");
    Lockr.set("age", 43);

    var name = Lockr.get("name");
    var age = Lockr.get("age");
    console.log(name, age + 1);

    //compare to localStorage
    localStorage.setItem("age_ls", 43);
    console.log(localStorage.getItem("age_ls")+1);

});

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

The code begins with two simple sets—a name and an age—and then fetches them and prints them to the console. Notice, though, that we add 1 to the age value. Immediately after this is a similar test using “regular” Web Storage. If you run this, you’ll discover something interesting, as shown in Figure 6-2.

Comparing Lockr and basic Web Storage
Figure 6-2. Comparing Lockr and basic Web Storage

Do you see it? When Lockr retrieved and then modified the numeric value, it worked correctly. But Web Storage treats everything as a string, so getting the age and “adding 1” ended up adding a 1 to the end of the value. OK, so that’s not a terribly big deal, but how about Example 6-2?

Example 6-2. lockr/test2.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="lockr.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {

    Lockr.set("stuff", [1,2,3,4]);
    Lockr.set("person", {
        name:"Ray",
        age:43,
        hobbies:["stuff","more stuff"]
    });

    var stuff = Lockr.get("stuff");
    var person = Lockr.get("person");
    console.dir(stuff);
    console.dir(person);

});

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

In this example, we’ve taken two complex objects and stored them with Lockr. We did not have to serialize them to JSON on storage or retrieval, as Figure 6-3 demonstrates.

Lockr deftly handles complex data
Figure 6-3. Lockr deftly handles complex data

But wait—it gets better. You can also use Lockr’s get API to return a default value when there isn’t an existing value in Web Storage:

var coolness = Lockr.get("coolness", "Infinity!");

In this snippet, if there wasn’t a value for coolness in Web Storage, then "Infinity!" will be returned. (You can find a full demo of this in lockr/test3.html.)

Lockr also supports a special type of value called a hash. Given an array of data, Lockr will let you add unique values to it. If you try to add a value that already exists, it will not be added again. So, for example, given an array that consists of three numbers, [1, 8, 9], if you try to add another 9, Lockr will not append it to the array. If you try to add 4, however, Lockr will allow it, so the array will now be [1, 8, 9, 4]. Lockr does this via a sadd API, as demonstrated in Example 6-3.

Example 6-3. lockr/test4.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="lockr.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {

    Lockr.set("testS", []);

    Lockr.sadd("testS", 1);
    Lockr.sadd("testS", 2);
    Lockr.sadd("testS", 3);
    Lockr.sadd("testS", 2);
    Lockr.sadd("testS", 2);
    Lockr.sadd("testS", 1);

    console.log(Lockr.get("testS"));
    console.log(Lockr.smembers("testS"));

    Lockr.srem("testS", 3);

    console.log(Lockr.smembers("testS"));

    console.log(Lockr.sismember("testS", 3));

});

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

To initialize the value, I set an empty array to the testS key. I then used sadd to add a number of values. After all those numbers are added, though, the only items in the array are 1, 2, and 3. You can use smembers to return all the values as well.

Next, srem is used to remove a value. When the members are returned again, now only 1 and 2 remain. Finally, sismember will return true or false if a value exists in the hash.

All in all, Lockr is a rather nice little library. Even with Web Storage being easy to use, the data handling aspects of Lockr alone are enough to interest me. When you throw in its incredibly small size, the library becomes even more appealing.

Simplifying IndexedDB with Dexie

For our next library, we’ll look at Dexie (shown in Figure 6-4), a far simpler wrapper for the somewhat complex IndexedDB API. As with all the libraries we’ll discuss in this chapter, it is 100% free and open source. You can find out more and download the library at http://www.dexie.org.

The Dexie website
Figure 6-4. The Dexie website

With Dexie, you have not one but three different ways to install the library. You can use Bower (bower install dexie), use npm (npm install dexie), or just download the bits from GitHub.

Once you have the library loaded on your web page, you’ll discover that working with Dexie is incredibly simple. For example, here is how you create a pointer to an IndexedDB database and initialize it with an object store called notes:

var db = new Dexie("name-here");
db.version(1).stores({
    notes:'text,created'
});
db.open();

You can probably guess that the string "text,created" refers to the expected properties of the data that will be stored, but Dexie goes a lot further and lets you pass simple tokens to these properties to define how they act. For example, this version will add a key called id that is automatically set.

var db = new Dexie("name-here");
db.version(1).stores({
    notes:'++id,text,created'
});
db.open();

In Example 6-4, you get a complete view of this in action.

Example 6-4. dexie/test1.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="Dexie.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {


    var db = new Dexie("dexie1");
    db.version(1).stores({
        notes:"++id,text,created"
    });
    db.open();

    console.dir(db);

});

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

Altogether this doesn’t do much, and the console.dir won’t be terribly helpful because it’s just an instance of a Dexie-wrapped database, but if you use your browser developer tools to look at your IndexedDB instances you’ll see that a new one called dexie1 has been created (Figure 6-5).

A new IndexedDB without the pain!
Figure 6-5. A new IndexedDB without the pain!

So far, so good, but let’s look at a few basic CRUD examples. Here is how you could add data.

db.notes.add(
{ text:'foo', created:new Date().getTime() }
).then(function() {
 console.log('Note added.');
}).catch(function(err) {
});

If you are familiar with promises, then this syntax will look familiar. It’s certainly simpler than the transaction API you use normally. (To be clear, Dexie is still using transactions behind the scenes, but you don’t have to worry about them for simple operations. While we won’t cover the topic in this simple introduction, if you want to do multiple CRUD operations in Dexie, it also has a transaction API you can use. And yes, it is still easier than the default IndexedDB API.)

How about reading? Yep—it is also simple:

db.notes.get(1).then(function(note) {
  console.dir(note);
});

Updating is slightly more complex—you pass in the key of the object you are modifying:

db.notes.put(
{ text:'foo', created:new Date().getTime(), key }
).then(function() {
 console.log('Note updated.');
}).catch(function(err) {
});

Even nicer, you can use put without a primary key and it will perform an insertion instead of an update. This lets you simply use put without switching between it and add.

And then finally, the delete operation:

db.notes.delete(1).then(function(note) {
  console.log("Removed");
});

Let’s modify our previous example to add a bit of data to the database (Example 6-5).

Example 6-5. dexie/test2.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="Dexie.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {


    var db = new Dexie("dexie1");
    db.version(1).stores({
        notes:"++id,text,created"
    });
    db.open();

    db.notes.add(
        { text:"foo",created:new Date().getTime() }
    ).then(function() {
         console.log("Note added.");
    }).catch(function(err) {
        console.dir(err);
    });

});

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

Now our template actually adds a bit of data. Typically, you would tie this to a form, but if you open it in your browser and check your developer tools, you’ll see the freshly added data (Figure 6-6).

New data added by Dexie
Figure 6-6. New data added by Dexie

The final piece of the puzzle is searching for data, and here is where Dexie really, really shines. Let’s consider a simple example—finding data where some particular column (or property) has a value lower than a target:

db.something.where("column").below(value).each(
function(item) {
  console.log('runs for each match')
});

Or maybe you meant higher, not lower:

db.something.where("column").above(value).each(
function(item) {
  console.log('runs for each match')
});

Or perhaps between two values:

db.something.where("column").between(value1, value2).each(
function(item) {
  console.log('runs for each match')
});

You can find a simple demonstration of this API in Example 6-6.

Example 6-6. dexie/test3.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="Dexie.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {


    var db = new Dexie("dexie3");
    db.version(1).stores({
        people:"email,name,age"
    });
    db.open();

    db.people.put({ email:"[email protected]", name:"Raymond", age:43 });
    db.people.put({ email:"[email protected]", name:"Elric", age:23 });
    db.people.put({ email:"[email protected]", name:"Zula", age:12 });

    db.people.where("age").between(20,50).each(function(person) {
        console.log("age match",JSON.stringify(person));
    });

    db.people.where("name").anyOf(["Elric","Zula"]).each(function(person) {
        console.log("name match",JSON.stringify(person));
    });
});

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

If you run this, you may notice something interesting about the output (see Figure 6-7).

Output from the Dexie search example
Figure 6-7. Output from the Dexie search example

While the results are correct, don’t forget that they are asynchronous results. That’s why you see the results “intermingled” in the console. While it’s not necessarily relevant to the book at hand, let’s quickly demonstrate how you could handle a case like this. We mentioned earlier that Dexie supports transactions, and transactions themselves support knowing when they’ve completed. Example 6-7 shows a modified version of the previous template that uses a transaction to wait for the query operations to complete.

Example 6-7. dexie/test4.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="Dexie.min.js"></script>
</head>

<body>

<script>

$(document).ready(function() {


    var db = new Dexie("dexie3");
    db.version(1).stores({
        people:"email,name,age"
    });
    db.open();

    db.people.put({ email:"[email protected]", name:"Raymond", age:43 });
    db.people.put({ email:"[email protected]", name:"Elric", age:23 });
    db.people.put({ email:"[email protected]", name:"Zula", age:12 });

    var ageResults, anyResults;
    db.transaction('r', db.people, function() {
        ageQuery = db.people.where("age").between(20,50).toArray().then(
        function(age) {
            ageResults = age;
        });
        anyQuery = db.people.where("name").anyOf(["Elric","Zula"]).toArray().then(
        function(any) {
            anyResults = any;
        });

    }).then(function() {
        console.log(JSON.stringify(ageResults));
        console.log(JSON.stringify(anyResults));
    });

});

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

Also note that we modified the second query to use the toArray helper. This is a utilty Dexie provides that can return the result of a query into a simple array, meaning you would not need to use the each method to iterate over each result.

Working with localForage

For our last and final library (but don’t forget, there’s more!), we’ll look at localForage (shown in Figure 6-8), an open source project by Mozilla, the folks behind Firefox. localForage is an ambitious client-side storage wrapper that supports IndexedDB, Web SQL, and Local Storage, selecting the best mechanism it can on the fly to store data on the user’s browser. You can find complete documentation, examples, and the downloads at the localForage website.

The localForage website
Figure 6-8. The localForage website

As with  the other libraries in the chapter, you have multiple ways of installing localForage. You can use Bower (bower install localforage), use npm (npm install localforage), or just download the code from GitHub.

localForage’s API is entirely asynchronous, but supports both the “old style” callback as well as “new and hot” Promise-based APIs. You can use whatever form you’re most comfortable with.

As a simple example, here is how you would set a value with a callback that is executed when the value is persisted:

localforage.setItem("name", value, function(err, value) {
});

And here is the Promise-style version:

localforage.setItem("name", value).then(function(value) { });

Both do the exact same thing (OK, technically I’d need a catch in my second example) so you can use whatever form seems easier for you. Retrieving a value works the same way:

localforage.getItem("name", function(err, value) { });
localforage.getItem("name").then(function(value) { });

Example 6-8 gives a simple demonstration of these read and writes in action.

Example 6-8. localForage/test1.html
<!doctype html>
<html>
<head>
    <script type="text/javascript" src =
    "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
    <script src="localforage.js"></script>
</head>

<body>

<script>

$(document).ready(function() {


    localforage.setItem("name", "Ray", function(err, value) {
        if(err) console.dir(err);
        console.log(value);
    });

    localforage.setItem("age", 43, function(err, value) {
        if(err) console.dir(err);
        console.log(value);

        localforage.getItem("age").then(function(value) {
            console.log("the value of age plus one is "+(value+1));
        });
    });

});

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

The first example simply sets a name value to Ray. The callback is fired when the data is successfully stored. Reporting the value back out to console is not very useful since it (obviously) matches what we passed in. The second example stores a numeric value, and to ensure it is stored correctly, we fetch it and add 1 to the value when done.

localForage also supports APIs for removing data (removeItem), clearing storage (clear), counting the number of keys (length), and fetching all the keys (keys). You can also iterate over all the key/value pairs with a simple iterate call:

localforage.iterate(function(value, key, index) {

}, callback);

As we said earlier, localForage attempts to use the “best” storage method it can on the current browser. By default, localForage will first try IndexedDB, then Web SQL, and finally Local Storage. What’s cool is that you can actually request a different priority. The setDriver API lets you specify which system you wish to use, or an array of systems you want to use in order. Here is an example that prefers Web SQL over IndexedDB:

localforage.setDriver(localforage.WEBSQL, localforage.INDEXEDDB);

Note that you do not need to specify Local Storage. If localForage cannot find your preferred storage system, it will fall back automatically.

localForage does not provide any query or search capabilities, so keep that in mind before adopting this particular library. This means localForage is better suited for simple storage needs, like large data sets you wish to retrieve via key rather than via some ad hoc search.

More Options

As we’ve said multiple times, we’ve covered only a few client-side storage libraries. Here are a few more you may want to consider checking out.

  • PouchDB is an incredibly powerful option. In fact, it was so powerful I was worried it was a bit too much to cover in a short form here in the chapter. The developers behind this library have done a huge amount of work in the area of client-side storage and are well-known experts in the field. One of the biggest features of PouchDB that may entice you is that it supports data synchronization.

  • lawnchair is an older library that also supports multiple storage methods via an adapter API.

  • And finally, you can also peruse libraries via this helpful list created by Juho Vepsäläinen.

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

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