Backbone.sync

This is responsible to handle connections between a RESTful server and the Backbone application is the Backbone.sync module. It transforms the fetch() and save() operations into HTTP requests:

  • fetch() is mapped as a read operation. This will make GET to the the urlRoot attribute with the model ID for a model or the url attribute for a collection.
  • save() is mapped as a create or update operation; it depends on the isNew() method:
    • This will be mapped as create if the model does not have an ID (isNew() method return true). A POST request is executed.
    • This will be mapped as update if the model already has an ID (isNew() method returns false). A PUT request is executed.
  • destroy() is mapped as a delete operation. This will make DELETE to the the urlRoot attribute with the model ID for a model or the url attribute for a collection.

To better understand how Backbone.sync does its job, consider the following examples:

// read operation will issue a GET /contacts/1
varjohn= new Contact({id: 1});
john.fetch();

// update operation will issue a PUT /contacts/1
john.set('name', 'Johnson');
john.save();

// delete operation will issue a DELETE /contacts/1
john.destroy();
varjane = new Contact({name: 'Jane'});
// create operation will issue a POST /contacts
jane.save();

As you can read in the Backbone documentation, Backbone.sync has the following signature:

sync(method, model, [options])

Here, the method is the operation that is to be issued (read, create, update, or delete). You can easily overwrite this function in order to redirect the requests to localStorage instead of a RESTful server:

Backbone.sync = function(method, model, options) {
var response;
var store = model.dataStore ||
              (model.collection&&model.collection.dataStore);
var defer = Backbone.$.Deferred();

  if (store) {
    // Use localstorage in the model to execute the query
    switch(method) {
      case 'read':
        response = model.id ?store.find(model) : store.findAll();
        break;

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

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

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

  // Respond as promise and as options callbacks
  if (response) {
defer.resolve(response);
    if (options &&options.success) {
options.success(response);
    }
  } else {
defer.reject('Not found');
    if (options &&options.error) {
options.error(response);
    }
  }

  return defer.promise();
};

While the localStorage API is synchronous, it does not need to use callbacks or promises; however, in order to be compatible with the default implementation, we need to create a Deferred object and return a promise.

If you don't know what a promise or Deferred objects are, please refer to the jQuery documentation for more information about it. The explanation of how promises work is out of the scope of this book.

The previous Backbone.sync implementation is looking for a dataStore attribute in the models/collections. The attribute should be included in these objects in order to be stored correctly. As you may guess, it should be an instance of our DataStore driver:

// apps/contacts/models/contact.js
class Contact extends Backbone.Model {
  constructor(options) {
    super(options);

this.validation = {
      name: {
        required: true,
minLength: 3
      }
    };

this.dataStore = new DataStore('contacts');
  }
  // ...
}

// apps/contacts/collections/contactCollection.js
class ContactCollection extends Backbone.Collection {
  constructor(options) {
    super(options);
this.dataStore = new DataStore('contacts');
  }

// ...
}

The implementation that we made earlier for localStorage is inspired from the Backbone.localStorage plugin. If you want to store all your models in the browser, please use the plugin that has the support of the community.

Due the limitations of localStorage, it is not suitable to store avatar images on it as we will reach the limits with only a few records.

Using localStorage as cache

The Datastore driver is useful to develop small applications that do not need to fetch and store the data in a remote server. It can be enough to prototype small web applications or store configuration data in the browser.

However, another use for the driver can be cache server response in order to speed up the application performance:

// cachedSync.js
var _ = require('underscore');
var Backbone = require('backbone');

function getStore(model) {
  return model.dataStore;
}

module.exports =  _.wrap(Backbone.sync, (sync, method, model, options) => {
var store = getStore(model);

  // Try to read from cache store
  if (method === 'read') {
    let cachedModel = getCachedModel(model);

    if (cachedModel) {
      let defer = Backbone.$.Deferred();
defer.resolve(cachedModel);

      if (options &&options.success) {
options.success(cachedModel);
      }

      return defer.promise();
    }
  }

  return sync(method, model, options).then((data) => {
    // When getting a collection data is an array, if is a
    // model is a single object. Ensure that data is always
    // an array
    if (!_.isArray(data)) {
      data = [data];
    }

data.forEach(item => {
      let model = new Backbone.Model(item);
cacheResponse(method, store, model);
    });
  });
});

When the application needs to read the data, it tries to read the data from localStorage first. If no model is found, it will use the original Backbone.sync function to fetch the data from the server.

When the server responds, it will store the response in localStorage for future use. To cache a server response, it should store the server response or drop the model from the cache when the model is deleted:

// cachedSync
function cacheResponse(method, store, model) {
  if (method !== 'delete') {
updateCache(store, model);
  } else {
dropCache(store, model);
  }
}

Dropping the model from the cache is quite simple:

function dropCache(store, model) {
  // Ignore if cache is not supported for the model
  if (store) {
store.destroy(model);
  }
}

To store and retrieve the data in the cache is more complex; you should have a cache expiration policy. For this project, we will expire the cached responses after 15 minutes, which means that we will remove the cached data and then make a fetch:

// cachedSync.js
// ...

const SECONDS = 1000;
const MINUTES = 60 * SECONDS;
const TTL = 15 * MINUTES;

function cacheExpire(data) {
  if (data &&data.fetchedAt) {
    let now = new Date();
    let fetchedAt = new Date(data.fetchedAt);
    let difference = now.getTime() - fetchedAt.getTime();

    return difference > TTL;
  }

  return false;
}

function getCachedModel(model) {
var store = getStore(model);

  // If model does not support localStorage cache or is a
  // collection
  if (!store&& !model.id) {
    return null;
  }

var data = store.find(model);

  if (cacheExpire(data)) {
dropCache(store, model);
    data = null;
  }

  return data;
}

The fetchedAt attribute is used to show the time we fetched the data from the server. When the cache expires, it removes the model from the cache and returns null to force a server fetch.

When a model is cached, it should set the fetchedAt attribute for the first time when it is fetched:

// cachedSync.js
function updateCache(store, model) {
  // Ignore if cache is not supported for the model
  if (store) {
varcachedModel = store.find(model);

    // Use fetchedAt attribute mdoel is already cached
    if (cachedModel&&cachedModel.fetchedAt) {
model.set('fetchedAt', cachedModel.fetchedAt);
    } else {
model.set('fetchedAt', new Date());
    }

store.update(model);
  }
}

Finally, we need to replace the original Backbone.sync function:

// app.js
varcachedSync = require('./cachedSync');

// ...

Backbone.sync = cachedSync;
..................Content has been hidden....................

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