IndexedDB in Backbone

As the IndexedDB API is more complex than localStorage, it will be more difficult to create an IndexedDB driver for Backbone as we did with localStorage; in this section, you will use what you have learned about IndexedDB in order to build a driver for Backbone.

The driver should open a database and initialize the stores when it is created for the first time:

// indexedDB/dataStore.js
'use strict';

var Backbone = require('backbone');

const ID_LENGTH = 10;

var contacts = [
  // ...
];

class DataStore {
constructor() {
this.databaseName = 'contacts';
  }

openDatabase() {
var defer = Backbone.$.Deferred();

    // If a database connection is already active use it,
    // otherwise open a new connection
    if (this.db) {
defer.resolve(this.db);
    } else {
      let request = indexedDB.open(this.databaseName, 1);

request.onupgradeneeded = () => {
        let db = request.result;
this.createStores(db);
      };

request.onsuccess = () => {
        // Cache recently opened connection
this.db = request.result;
defer.resolve(this.db);
      };
    }

    return defer.promise();
  }

createStores(db) {
var store = db.createObjectStore('contacts', {keyPath: 'id'});

    // Create the first records
contacts.forEach(contact => {
store.put(contact);
    });
  }
}

When the connection is opened, it creates the contacts store and puts the first records in the store. After that it caches the database handler in the db attribute to reuse the connection for future requests.

Now, we should create the necessary method to create, update, delete, and read the data from the store:

// indexedDB/dataStore.js

var crispy = require('crispy-string');

// ...

class DataStore {
  create(model) {
var defer = Backbone.$.Deferred();

    // Assign an id to new models
    if (!model.id&& model.id !== 0) {
      let id = this.generateId();
model.set(model.idAttribute, id);
    }

    // Get the database connection
this.openDatabase()
.then(db =>this.store(db, model))
.then(result =>defer.resolve(result));

    return defer.promise();
  }

generateId() {
    return crispy.base32String(ID_LENGTH);
  }
  // ...
}

When a record is created, we should ensure that the model has an ID. We can generate it for the models that do not have an ID assigned. The store() method will put the record in the indexedDB database:

// indexedDB/dataStore.js

var crispy = require('crispy-string');

// ...

class DataStore {
  // ...

store(db, model) {
var defer = Backbone.$.Deferred();

    // Get the name of the object store
varstoreName = model.store;

    // Get the object store handler
vartx = db.transaction(storeName, 'readwrite');
var store = tx.objectStore(storeName);

    // Save the model in the store
varobj = model.toJSON();
store.put(obj);

tx.oncomplete = function() {
defer.resolve(obj);
    };

tx.onerror = function() {
defer.reject(obj);
    };

    return defer.promise();
  }

  // ...
}

The store() method obtains the name of the store from the modelstore attribute and then, creates a readwrite transaction for the given store name to put the record on it. The update() method uses the same store() method to save the record:

// indexedDB/dataStore.js
class DataStore {
  // ...

  update(model) {
var defer = Backbone.$.Deferred();

    // Get the database connection
this.openDatabase()
.then(db =>this.store(db, model))
.then(result =>defer.resolve(result));

    return defer.promise();
  }

  // ...
}

The update method does not assign an ID to the model, it completely replaces the previous record with the new model data. To delete a record, you can use the delete() method of the object store handler:

// indexedDB/dataStore.js
class DataStore {
  // ...

destroy(model) {
var defer = Backbone.$.Deferred();

    // Get the database connection
this.openDatabase().then(function(db) {
      // Get the name of the object store
      let storeName = model.store;

      // Get the store handler
vartx = db.transaction(storeName, 'readwrite');
var store = tx.objectStore(storeName);

      // Delete object from the database
      let obj = model.toJSON();
store.delete(model.id);

tx.oncomplete = function() {
defer.resolve(obj);
      };

tx.onerror = function() {
defer.reject(obj);
      };
    });

    return defer.promise();
  }

  // ...
}

To get all the models stored on an object store, you need to open a cursor and put all the items in an array, as follows:

// indexedDB/dataStore.js
class DataStore {
  // ...

findAll(model) {
var defer = Backbone.$.Deferred();

    // Get the database connection
this.openDatabase().then(db => {
      let result = [];

      // Get the name of the object store
      let storeName = model.store;

      // Get the store handler
      let tx = db.transaction(storeName, 'readonly');
      let store = tx.objectStore(storeName);

      // Open the query cursor
      let request = store.openCursor();

      // onsuccesscallback will be called for each record
      // found for the query
request.onsuccess = function() {
        let cursor = request.result;

        // Cursor will be null at the end of the cursor
        if (cursor) {
result.push(cursor.value);

          // Go to the next record
cursor.continue();
        } else {
defer.resolve(result);
        }
      };
    });

    return defer.promise();
  }

  // ...
}

Note how this time the transaction opened is in the readonly mode. A single object can be obtained by querying the model ID:

// indexedDB/dataStore.js
class DataStore {
  // ...

  find(model) {
var defer = Backbone.$.Deferred();

    // Get the database connection
this.openDatabase().then(db => {
      // Get the name of the collection/store
      let storeName = model.store;

      // Get the store handler
      let tx = db.transaction(storeName, 'readonly');
      let store = tx.objectStore(storeName);

      // Open the query cursor
      let request = store.openCursor(IDBKeyRange.only(model.id));

request.onsuccess = function() {
        let cursor = request.result;

        // Cursor will be null if record was not found
        if (cursor) {
defer.resolve(cursor.value);
        } else {
defer.reject();
        }
      };
    });

    return defer.promise();
  }

  // ...
}

In the same way as we did with localStorage, this IndexedDB driver can be used to overwrite the Backbone.sync function:

// app.js
var store = new DataStore();

// ...

Backbone.sync = function(method, model, options) {
var response;
var defer = Backbone.$.Deferred();

  switch(method) {
    case 'read':
      if (model.id) {
        response = store.find(model);
      } else {
        response = store.findAll(model);
      }
      break;

    case 'create':
      response = store.create(model);
      break;

    case 'update':
      response = store.update(model);
      break;

    case 'delete':
      response = store.destroy(model);
      break;
  }

response.then(function(result) {
    if (options &&options.success) {
options.success(result);
defer.resolve(result);
    }
  });

  return defer.promise();
};

Then, models should add the store attribute to indicate in which object store the model will be saved:

class Contact extends Backbone.Model {
  constructor(options) {
// ,,,
this.store = 'contacts';
  }

  // ...
}

class ContactCollection extends Backbone.Collection {
  constructor(options) {
// ...
this.store = 'contacts';
  }

// ...
}

IndexedDB allows you to store more data than localStorage; therefore, you can use it to store the avatar image too. Just make sure that the avatar attribute is set so that an image is always selected:

class ContactPreview extends ModelView {
  // ...

fileSelected(event) {
event.preventDefault();

var $img = this.$('img');

    // Get a blob instance of the file selected
var $fileInput = this.$('#avatar')[0];
varfileBlob = $fileInput.files[0];

    // Render the image selected in the img tag
varfileReader = new FileReader();
fileReader.onload = event => {
      $img.attr('src', event.target.result);

this.model.set({
        avatar: {
url: event.target.result
        }
      });
    };
fileReader.readAsDataURL(fileBlob);

this.trigger('avatar:selected', fileBlob);
  }
}

Do not try to upload the image:

class ContactEditor {
// ...

showEditor(contact) {
    // ...

    // When avatar is selected, we can save it inmediatly if the
    // contact already exists on the server, otherwise just
    // remember the file selected
    //this.listenTo(contactPreview, 'avatar:selected', blob => {
    //  this.avatarSelected = blob;

    //  if (!contact.isNew()) {
    //    this.uploadAvatar(contact);
    //  }
    //});
  }
saveContact(contact) {
// ...

    // The avatar attribute is read-only
    //if (contact.has('avatar')) {
    //  contact.unset('avatar');
    //}

// ...
  }

  // ...
}
..................Content has been hidden....................

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