Backbone: synchronizing Models and LocalStorage

103 Views Asked by At

I've extended a model A and a collection of As as follows:

define(['underscore', 'backbone', 'backbone.localStorage'], function(_, Backbone) {
   var A = Backbone.Model.extend({
      initialize: function() {          
      }
   });

   var A_Collection = Backbone.Collection.extend({
      model: A,
      localStorage: new Backbone.LocalStorage("as")
   });

   return {
      Model: A,
      Collection: A_Collection 
   };
});

Collections are stored in localStorage and all works fine in my application. Then I clear and replace the localStorage directly by code (using clear and setItem functions) and try to instantiate a new collection, but the changes are not detected:

var aux = new A.Collection();
aux.fetch(); 
// aux is empty

Otherwise if a try:

var aux = new A.Collection();
aux.localStorage = new Backbone.LocalStorage("as");
aux.fetch(); 
// aux contains new data 

The latter is not valid for me because I'd have to modify all the creation of collections in my project.

What am I missing?

1

There are 1 best solutions below

1
Louis On BEST ANSWER

Instances of Backbone.LocalStorage are not designed to listen for LocalStorage changes that occur outside their own code. That's why you get the behavior you are getting. However, there is a workaround.

When you define a collection like this:

var A_Collection = Backbone.Collection.extend({
   model: A,
   localStorage: new Backbone.LocalStorage("as")
});

the localStorage value is shared by all the instances of A_Collection. You can automatically create a new instance of Backbone.LocalStorage, like this:

var A_Collection = Backbone.Collection.extend({
  model: A,
  initialize: function() {
    A_Collection.__super__.initialize.apply(this, arguments);
    A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
  },
});

We have to set it on the prototype so that it is shared by all instance of A_Collection, which is the same behavior as your original code. With this in place, whenever you create a new instance of A_Collection, you will get a new instance of Backbone.LocalStorage, which will get information anew from LocalStorage.

Here is a plunker illustrating. Here is the relevant code, for reference:

var A = Backbone.Model.extend({
  initialize: function() {}
});

var A_Collection = Backbone.Collection.extend({
  model: A,
  initialize: function() {
    A_Collection.__super__.initialize.apply(this, arguments);
    A_Collection.prototype.localStorage = new Backbone.LocalStorage("as");
  },
});

// Setup a collection.
var collection = new A_Collection();
collection.fetch();

// Clean it out from previous runs... Note that we have to use destroy to destroy all items. 
// Reset won't save to LocalStorage.
while (collection.length > 0) {
  var model = collection.at(0);
  model.destroy();
  collection.remove(model);
}
// and set some elements.
collection.create({
  name: "1"
});
collection.create({
  name: "2"
});
console.log("collection length:", collection.length);

// Mess with it outside the Backbone code.
localStorage.clear();
// Manually create data that looks like what Backbone expects.
localStorage.setItem("as-1", JSON.stringify({
  name: "foo",
  id: "1"
}));
localStorage.setItem("as-2", JSON.stringify({
  name: "bar",
  id: "2"
}));
localStorage.setItem("as-3", JSON.stringify({
  name: "baz",
  id: "3"
}));
localStorage.setItem("as", "1,2,3");

// Create a new collection that loads from LocalStorage
var collection2 = new A_Collection();
collection2.fetch();
console.log("collection 2 length:", collection2.length);
console.log("first item", collection2.at(0).toJSON());
console.log("third item", collection2.at(2).toJSON());

console.log("instance is shared?", collection.localStorage === collection2.localStorage);

The code above generates this on the console:

collection length: 2
collection 2 length: 3
first item Object {name: "foo", id: "1"}
third item Object {name: "baz", id: "3"}
instance is shared? true