Newly saved Backbone model does not appear in collection after fetch - javascript

I am using backbone local storage and experiencing some weird behavior.
I have a Model and Collection that is defined, instantiated, and fetched:
MyModel = Backbone.Model.extend({
localStorage: new LocalStore('example-myModels')
//note: LocalStore = Backbone.LocalStore -> https://github.com/jeromegn/Backbone.localStorage
});
MyCollection = Backbone.Collection.extend({
model : MyModel,
localStorage: new LocalStore('example-myModels')
});
var myCollection = new MyCollection();
myCollection.fetch(...);
This collection is then displayed as a list to the user. The user is able to "add" an item to the collection which eventually results in this code:
var newModel = new MyModel();
newModel.save(newModelAttributes, {
success: function(newlySavedModel) {
myCollection.add(newlySavedModel);
}
);
At this point myCollection has the newly added model and I can see the record successfully created in my localStorage database:
Pre-save LocalStorage:
Post-save LocalStorage:
The next step after the user adds the record is to go back to the list, at which point the collection is fetched again:
myCollection.fetch();
Now myCollection no longer contains the new record. No matter how many times I fetch, the new record will not be retrieved - even though I can see it in my localStorage database. I have also tried creating a new instance of the Collection and fetching that but it yields the same results. If I reload the browser, the new record appears as expected. Anyone have any idea what is going on? I get the feeling I am missing something fundamental here...
A running example that reproduces the issue is available here: http://jsbin.com/iyorax/2/edit (make sure the console is visible and click "Run with JS")
Thanks in advance!

Your model and your collection need to share a reference to the same instance of LocalStore, whereas right now you are creating two different objects. To fix this, create the LocalStore object outside of either model or collection, and pass in a reference to it in the constructors of your Model and collection.

I have an application working with Backbone.localstorage and what works for me is I first add the model to the collection and then I call save to the model. like this.
var newModel = new MyModel();
myCollection.add(newModel)
newModel.save();
Im not 100% sure, but my reasoning is that, your model is saved, but your collection is not, and perhaps an index is not being updated and at the time that you call fetch you are getting the unupdated collection.
Hope that helps.

The only reason I can think of due to which the model is not present in the collection is because of Duplicated ID's
I don't see a IdAttribute defined on the model. That could be a problem sometimes.

Related

Prevent Backbone collection reset

I am new to Backbone. I see this in every backbone app:
var List = Backbone.collection.extend({
model: model
});
var myList = new List();
I am a bit confused about this. This script is included in a page, and when the page is reloaded or opened again and again, it will keep instantiate new collection doesn't it?
Whenever I save some models into this collection, things are still fine. But when I start to reload the page or open the page again, it will instantiate new collection with the same name again and the collection becomes empty again.
Any suggestions to prevent this? I want collection keep the models even if reloaded.
Use myList.fetch() in your view to load data from your api resource.
Some more info at BB site
Edit:
You can save model by using Collection create
So first instantiate new collection, then use
Collection.create({
name: 'John'
});
You can observe your network log to see what was posted to the api.
For your example:
var List = Backbone.collection.extend({
model: model
});
var myList = new List();
myList.create({
name: 'John'
});

How do I remove a model from a Backbone.Collection

I want to remove a model from a collection:
var item = new Backbone.Model({
id: "01",
someValue: "blabla",
someOtherValue: "boa"
});
var list = new Backbone.Collection([item]);
list.get("01").destroy();
Result:
item is still in the backbone array ....
I have reservations about the accepted answer. When a model is destroyed, the "destroy" event bubbles through any collection the model is in. Thus, when you destroy a model you should not have to also remove the model from the list.
model.destroy();
Should be enough.
Looking at your code it looks correct: (If the intent is to destroy + remove, not just remove)
list.get('01').destroy();
Are you sure that your resource is getting properly destroyed? Have you tried placing a success and error callback in your destroy() call to ensure the destroy was executed? For example, if your model URL is incorrect and it can't access the resource, the destroy call would return an error and your collection will still have the model. This would exhibit the symptoms you outline in your question.
By placing the remove() after the destroy call, your collection will definitely remove the model. But that model will still be floating around (still persisted). This may be what you want, but since you're calling destroy() I'm assuming you want to obliterate it completely. If this is the case, while remove seems to work, what it's really doing is masking that your model has been destroyed when in fact it may not.
Thus, this is what I have a feeling is actually happening. Something is preventing the model from being destroyed. That's why when you call destroy(), then check your collection - the model is still there.
I could be wrong though. Could you check this and update your findings?
You should also remove the model from the collection.
var model = list.get('01');
model.destroy();
list.remove(model);

Backbone: Adding a model to a collection from a collection view?

I have some code where I want a NoteCollectionView to add a new Note to the NoteCollection. This is triggered by a function newNote in the NoteCollectionView:
newNote: function(data) {
var note = new Note(data);
this.collection.add(note);
},
I'm still very new to backbone, and I want to make sure this syncs with the server. The concerns I have are:
1) Will simply adding this note to the collection trigger a save() from the server, and update the model with the ID that the server gives it? Or,
2) If the server does not update my model and give me an actual ID, how do I save the model with note.save() and get back an ID from the server?
To address your first question, no, .add will not trigger any kind of call to the server; it will only add a model to a collection.
However, you do have a couple options. One would be to create the new note model, save it to the database, and then add it to the collection:
newNote: function(data) {
var note = new Note(data);
note.save();
this.collection.add(note);
}
The second option is to simply use Backbone's collection.create method. Give it a hash of attributes and it will
Create the model
Save it to the database
Add it to the collection
All in one fell swoop, like so:
newNote: function(data) {
return this.collection.create(data);
}
collection.create also returns the newly created model, illustrated by my return statement above.

Backbone.js and localStorage plugin relation between collection and model -- based on official 'todo' example

Here's the official sample app:
http://documentcloud.github.com/backbone/docs/todos.html
I am confused about the relationship between the collection and its property localStorage = new Store(..)
Shouldn't this be in the model because you can't do a collection.save() anyway?
In addition, I tried implementing something like it, and it doesn't work
var Person = Backbone.Model.extend({
defaults: {
name:'no-name',
age:0
}
});
var Persons = Backbone.Collection.extend({
model: Person,
localStorage: new Store('Persons'),
initialize: function(){
console.log('collection initialized');
}
});
window.people = new Persons();
var p1 = new Person({name:'JC',age:24});
p1.save({text:'hello'}); //<--- Uncaught TypeError: Cannot read property 'localStorage' of undefined
Can anyone help me figure this out?
It's actually the .create() function of a collection that allows the collection to "save" to localStorage.
source-code of todo sample:
createOnEnter: function(e) {
var text = this.input.val();
if (!text || e.keyCode != 13) return;
Todos.create({text: text});
this.input.val('');
},
This then allows the model instance to manipulate it using the .save({attr:value}) function.
Calling modelInstance.save() without a defined localStorage property in the model's constructor function will cause the error:
Uncaught TypeError: Cannot read property 'localStorage' of undefined
However, being that the model is now saved in the localStorage through the collectionInstance.create() method, modelInstance.save({attr:value}) can now be used to modify it.
So, in conclusion, Models only has the save() function which allows persistence, but the Collection object has the create() function that allows persistence.
In order to use these, REST urls within the collection and model must be properly set up or the localStorage plugin must be instantiated within the Constructor Function of either (depending on setup)
I had a similar problem in that I wanted to simply 'save' a collection that I had loaded from LocalStorage. I wrote a save() method on my Collections that simply looped through each model and called model.save().
MyCollection.save = ->
#each (model) ->
model.save()
There's a big drawback to this, however, with regards to Backbone.LocalStorage. You loose all the great benefits of using Collection.set({ models... }); to update your collection (pulling in an online update, or something) with all the add/merge/delete goodness. Removing the model from your collection at runtime doesn't delete it from local storage, and manually identifying unmatched models and destroying them somewhat defeats the purpose of Backbone.Collection.set();
One solution I found was to augment Backbone such that Backbone.Collection.set() uses destroy() instead of remove() on the models it finds to be missing. (see line 705 of BB 1.0.0)
Another solution, which I wound up going with, was to have all models listen for their own 'remove' event and call their own 'destroy' method when it happens. This allows Backbone.Collection.set()'s removals to become permanent.
class Model extends Backbone.Model
initialize: ->
#on 'remove', #destroy
Both of these mean you can't "remove" a model without destroying it permanently, which was fine for me. If you want, you could probably create some special conditions under which this occurs, and manage it that way.

Fetching a single Backbone model from server

Say I have a route setup:
'photos/:id' : 'showPhoto'
and somebody shares the url: www.mysite.com/photos/12345 with a friend.
When their friend clicks on the shared link, showPhoto gets called back with 12345 passed as the id. I cant figure out how to fetch the model from the server, because even when setting its id property and calling fetch(), backbone thinks that the model isNew and so the ajax request url is just /photos instead of /photos/12345:
showPhoto: (id) ->
photo = new models.Photo _id:id
photo.fetch #does a GET to /photos, I would have expected it to request /photos/12345
success: () ->
render photo view etc...
Photo = Backbone.Model.extend
idAttribute: '_id'
urlRoot: '/photos'
The model Photo is usually part of a collection, but in this scenario someone is visiting the site directly and only expects to see data for one photo, so the collection is not instantiated in this state of the app.
Is the solution to load the entire collection of photos and then use collection.getById(id)? This just seems way too inefficient when I just want to load the properties for one model.
if you don't have the model as part of a collection, you have to tell the model the full url manually. it won't auto-append the id to the urlRoot that you've specified. you can specify a function as the urlRoot to do this:
Photo = Backbone.Model.extend({
urlRoot: function(){
if (this.isNew()){
return "/photos";
} else {
return "/photos/" + this.id;
}
}
});
Backbone uses the id of the model to determine if it's new or not, so once you set that, this code should work correctly. if it doesn't, you could always check for an id in the if-statement instead of checking isNew.
You do not need to tell backbone whether or not to append the id the url. Per the documentation: http://backbonejs.org/#Model-fetch, you may simply set the urlRoot to the equivalent of the url in a collection.
Backbone will automatically append the desired id to the url, provided you use one of the following methods:
model.set("id", 5); //After initialized
model = new Backbone.Model({id: 5}); //New model
If you manually set the id in the attributes hash or directly on the model, backbone won't be aware of it.
model.id = 5; //Don't do this!
there's already a similar question: "How do I fetch a single model in Backbone?"
my answer there should work for you (and it's in coffeescript)
also remember to check Backbone Model#url documentation, it's all explained there
I would bootstrap the collection (by rendering the following to the page) with just one model in it like this:
photos = new PhotoCollection();
photos.reset([ #Html.ToJson(Model) ]);
Note that the server side code that I use is ASP.Net MVC so use something specific to your server side architecture. Also note that the square brackets are important as they take your singular model and wrap it in an array.
Hope that's helpful.

Categories