acimutal
acimutal

Reputation: 2255

Backbone: synchronizing Models and LocalStorage

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?

Upvotes: 1

Views: 100

Answers (1)

Louis
Louis

Reputation: 151491

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

Upvotes: 1

Related Questions