Why does each model in my collection have a collection? - backbone.js

The returned json is as expected but when i console.log the response i get this mad structure that seems to go on forever. Each of my models is gaining a collection of the exact collection it itself is part of. What on earth is going on?
Heres the code im calling it with
this.reporterCollection.fetch({
success: _.bind(function(response){
console.log("response");
console.log(response);
this.addChildView(new DirectReportListView({collection: response, supervisor: this.searchAlias, projectId: this.options.projectId}).render(), $("#testModalDiv"));
}, this)
});
Ignore the view stuff, the problem occurs before on the log line.

It's normal, each model in a collection have a collection field that points to the collection containing it.
The model.collection property is normally created automatically when you first add a model to a collection.
Documentation

Related

backbone collection fetch() returns one record only

I am trying to fetch list of objects from server, the collection accepts the first record and ignores the rest.
the response from the server is of type application/json
[{"id":1,"name":"A"},
{"id":2,"name":"B"},
{"id":3,"name":"C"}]
in client side,
var collection = new Backbone.Collection;
collection.url = 'url_to_the_resource';
collection.fetch();
console.log( collection.toJSON());
the output is
[{"id":1,"name":"A"}]
edit
I call log when the collection is ready, i.e. after the asynchronous call is complete as follows:
collection.fetch().done( function() {
console.log( collection.toJSON() );
});
and still getting one record. I have also checked backbone.js source code, and found the following at line 682
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(model)) {
and added a logging right after that line, and found that backbone merges all models, why?
I can only guess. If you can provide collection code it would be great.
Be sure model idAttribute (if it is overridden) is unique.
Check your collection parse method if it is overridden. It should return the array of objects.
Try to play with add, remove, merge
http://backbonejs.org/#Collection-set.
I found the reason,
it was because I have attached an event on the model prototype
Backbone.Model.prototype.on('request',function(model, xhr, options){ ... });
Most likely is the Override issue,
even each record of the original JSON data is totally different at all.
Revoke idAttribute property in Model or revoke modelId method in Collection may help.
You could use .findWhere({_id: pkId}) instead of .get(pkId)

backbone.js model and collection overhead

When I fetch models or collections from the server, I am not able to access properties of the model unless I stringify then re-parse. Presumably the models themselves have some extra overhead from backbone.js? Note that in the below code I can perform stringify/parse sequentially, which is supposed to give the same result as I started with. However, clearly I have killed off some superfluous info by performing these two steps because my model's properties are now exposed differently from before. Surely I do not need to go through these two steps to access my model properties, right?
Eg.
thismodel = /// assume this came from server fetch
alert(thismodel.name); // DOES NOT WORK - undefined
jsonmodel = JSON.stringify(thismodel);
var providerprefslistJSON = jQuery.parseJSON(jsonmodel);
alert(providerprefslistJSON.name); // WORKS
Backbone Model objects are not plain old JavaScript objects. They keep their attributes in an internal hash. To access the name attribute you can either do this:
alert(thismodel.attributes.name);
Or better yet use the get() method:
alert(thismodel.get("name"));
The reason it works when you convert the model to JSON and then back again is because JSON.stringify calls the toJSON() method, which creates a JSON string from the internal attributes hash, meaning when you parse that string you get a plain old JavaScript object - which is not the same as a Backbone Model object.
First, are you trying to access the property of the model or response?
From alert(thismodel.name) it would seem that you're going for a property of the model not the attribute. If you're looking for the model attribute then perhaps you want alert(this.model.get('name'))
If you're indeed going for model.name, then basically the problem may lie in how you're parsing the data. Say for example the JSON from your server is like this {'name':'Jimmy'}.
While the model.response the raw JSON sent has "Jimmy" namespaced under object.name, Backbone will automatically take that and turn it into a model attribute unless instructed otherwise (e.g. modelObj.attributes.name) at which point you'd use the get() function.
You should be able to access model data fairly simply if everything works.
E.g. Fetch
var model = new MyModel();
model.id = 1;
model.fetch({
success: function(model, response) {
console.log(model.get('name')); // The model name attribute
console.log(response.name); // The RAW response name property
}
});
Or maybe your server isn't sending the data back as JSON data. Is the server response content-type="application/json" ?
Some things to check.

How to generate model id's with Backbone.js

I am setting up the backbone sync mechanism and am a bit confused where to generate the id's for the models.
When I create a new model, should backbone be generating and setting the id, or am i supposed to implement an id generation method, or is there some sort of mechanism where I "PUT" the data to the server, which generates the id and returns a model with the id?
I'm providing a second answer to simplify the code you need to study to get the main points you're pondering about - the actual round about from model to server and how ids play their role.
Say you define a model - Let's go with Jurassic Park.
// Define your model
var Dinosaur = Backbone.Model.extend({
defaults: {
cavemanEater: undefined // Has one property, nom nom or not.
},
urlRoot: 'dino' // This urlRoot is where model can be saved or retrieved
});
var tRex = new Dinosaur({'cavemanEater':true});
You now have instantiated a dinosaur that is a meat eater. Roar.
console.log(tRex);
What you should notice is that in the properties of tRex, your model does not have an id. Instead, you will see a cID which you can think of as a temporary id that Backbone automatically assigns to your models. When a model doesn't have an id it is considered new. The concept of persisting a model (either to a database or local storage) is what allows you to go back to that resource after you've created it and do things like save (PUT) or destroy (DELETE). It would be hard to find that resource if you had no way to point directly at it again! In order to find that resource, your model needs an id, something it currently does not have.
So as the above answers have explained it is the job of your database (or localstorage, or some other solution) to provide Backbone with a resource id. Most of the time, this comes from the resource id itself, aka - the primary key id of your model in some table.
With my setup, I use PHP and mySQL. I would have a table called Dinosaur and each row would be a persistent representation of my dino model. So I'd have an id column (unique auto-incrementing int), and cavemanEater (bool).
The data communication flow happens like this.
You create a model.
The model is new so it only has a cID - no proper ID.
You save the model.
The json representation of your model is SENT to your server (POST)
Your server saves it to the table and gives it a resource id.
Your server SENDS BACK a json representation of the data {id:uniqueID}
Backbone RECEIVES this json representation with id
Backbone automagically updates your model with an id.
Here is what annotated code looks like.
CLIENT:
tRex.save();
// {'cavemanEater':true} is sent to my server
// It uses the urlRoot 'dino' as the URL to send. e.g. http://www.example.com/dino
SERVER:
// Is setup to accept POST requests on this specific ROUTE '/dino'
// Server parses the json into something it can work with, e.g. an associative array
// Server saves the data to the database. Our data has a new primary id of 1.
// Data is now persisted, and we use this state to get the new id of this dino.
$dinoArray = array('id'=>1, 'cavemanEater'=>true);
$dinoJSON = json_encode($dinoArray);
// Server does something to send $dinoJSON back.
CLIENT:
// If successful, receives this json with id and updates your model.
Now your tRex has an id = 1. Or should I say...
tRex.toJSON();
// RETURNS {'id':'1', 'cavemanEater':'true'}
Congrats. If you do this tRex.isNew() it will return false.
Backbone is smart. It knows to POST new models and PUT models that already have a resource id.
The next time you do this:
tRex.save();
Backbone will make a PUT request to the following URL.
http://www.example.com/dino/1
That is the default behavior by the way. But what you'll notice is that the URL is different than save. On the server you would need a route that accepts /dino/:id as opposed to /dino
It will use the /urlRoot/:id route pattern for your models by default unless you tweak it otherwise.
Unfortunately, dinosaurs are extinct.
tRex.destroy();
This will call... Can you guess? Yep. DELETE request to /dino/1.
Your server must distinguish between different requests to different routes in order for Backbone to work. There are several server side technologies that can do this.
Someone mentioned Sinatra if you're using Ruby. Like I said, I use PHP and I use SLIM PHP Framework. It is inspired by Sinatra so it's similar and I love it. The author writes some clean code. How these RESTful server implementations work is outside the scope of this discussion though.
I think this is the basic full travel of new Backbone data with no id, across the internets to your server where it generates, and sends back the resource id, to make your model live happily ever after. (Or destroy() not...)
I don't know if this is too beginner for you but hopefully it will help someone else who runs into this problem. Backbone is really fun to program with.
Other similar Answers:
Ways to save Backbone JS model data
or is there some sort of mechanism where I "PUT" the data to the server, which generates the id and returns a model with the id?
Kind of. When you call the save method of your model, backbone make a POST XHR and your application server should respond with JSON contains an id.
You can see an example here: http://addyosmani.com/blog/building-backbone-js-apps-with-ruby-sinatra-mongodb-and-haml/
Quoting from the link:
post '/api/:thing' do
# parse the post body of the content being posted, convert to a string, insert into
# the collection #thing and return the ObjectId as a string for reference
oid = DB.collection(params[:thing]).insert(JSON.parse(request.body.read.tos))
"{\"id\": \"#{oid.to_s}\"}"
end
If you don't know Ruby keep in mind what the last expression that is evaluated is automatically returned by the method.
What I understand from your question is that you want to have a collection with models that exist on the server. In order to get these models into the collection you'd have to add call 'fetch()' on the collection.
The url would be "/users" or something similar, that would have to return an array of objects with user data in there. Each item in the array would then be passed to UserCollection.add(). Well, actually it would be passed all at once, but you get the point.
After this your collection is populated. The url on the Model is meant for updating and saving the individual model. The url of the collection will also be used for creating models. Backbone's sync is RESTful, like Ruby on Rails. You can actually learn more about it on the documentation of Ruby on Rails:
http://guides.rubyonrails.org/routing.html
What you would generally do is have a different url for your model than for your controller. After populating your collection you have id's for each model in there because they came from the server.
Now when you add a new model based on user input you'd do something like this:
var HomeModel = Backbone.Model.extend({
defaults: {
lead: "not logged in",
},
url: 'test.php',
initialize: function(){
_.bindAll(this, 'handleSave', 'handleError');
// Save already knows if this.isNew.
this.save(undefined, {success: this.handleSave, error: this.handleError});
},
handleSave: function(model, response){
this.model.reset(model);
},
handleError: function(){
},
});
var HomeView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, 'render');
this.model = new HomeModel();
this.model.bind("change", this.render);
},
el: 'div',
render: function() {
// Do things to render...
}
});
var homeView = new HomeView();
The example is from someone else's question I answered, I just add the relevant things.
The general idea is to save the model when it is created, if you need it somewhere else you can just move the code into a function of the model and call that based on events or anything else.

How to populate a Backbone.js collection's _byId array so that I can use `get` on it?

I have a collection, and the collection.models returns an array of models. However, when I call collection.get(someId) (and this id is the id of the model that is in the collection.models array), I get undefined. Looking at collection._byId, it looks like an empty object.
How do I properly populate _byId, so that I can use get? Or perhaps I'm doing something wrong when initializing my collection, which is why _byId is empty.
I'm a little late, but hopefully this is still useful to some other people.
Collection._byId is just a normal js hash object. There's really nothing fancy about it. If you want Collection.get to work, just add all the models into the _byId hash.
Inside the collection's scope:
var someId = '123'; // any id will do
this._byId[someId] = someModel; // someModel.id = '123'
console.log(!!this.get(someId)); // should return true
Since I'm using this with Rails, the default json generated by Rails doesn't work well with Backbone. I don't know why I didn't see it while trying to learn Backbone. Anyway, you could either:
Change the way Rails generates its JSON
Change the way your Backbone app reads the JSON.
Sounds like the OP had a slightly different problem, but I experienced a similar issue and thought I'd post what worked for me.
Like the original issue, collection.models contained the right model, but in my case, the _byId hash contained a cid version of the model that wasn't empty. Nevertheless, _byId didn't contain a model with normal id (there's usually two version - an id one and a cid one), so I wasn't able to use collection.get(id) to retrieve it. My problem became a bit clearer when I read up about cid. From the docs:
Client ids are handy when the model has not yet been saved to the server, and does not yet have its eventual true id, but already needs to be visible in the UI.
I didn't think it was a problem with waiting for the server as my cid model and the collection.model had the correct ids. However passing in { wait : true } as an option in collection.create fixed this issue for me.

Prevent Backbone.js model from validating when first added to collection

Is there a way to suppress model validation in Backbone.js when a new model is first created?
In my app, I have a collection with an arbitrary number of models, which are represented as list items. The user can click a button on each item, which inserts a new empty item beneath the current item. The empty item is failing validation, obviously, because I don't want an empty item being saved later.
There's no way for me to know what sensible defaults might be when I'm creating the new item, so prepopulating the new model with valid data doesn't seem like an option.
Any suggestions?
Update: While working on a tangentially related problem, I realized that I was using Backbone.js version 0.9.0. When this version was released, other people had the same problem I was having, and they complained in this issue on GitHub.
Jeremy modified validation in 0.9.1 to fix this. Adding a (temporarily) empty model to a collection is a valid real-world usecase. You could handle the new, empty model in the view, but if you're managing a list of items like I am, that forces you to have a collection of item views (including the empty one) in addition to your collection of must-be-valid models. That's a really clunky workaround for an otherwise straightforward scenario. Glad this got fixed.
You're not supposed to add invalid models :)
Digging a bit in Backbone source code (0.9.1 at least) showed that the mechanism can be circumvented by passing options to your add method:
var Mod=Backbone.Model.extend({
validate: function(attrs,opts) {
if (opts.init) return;
return "invalid";
}
});
var Col=Backbone.Collection.extend({
model:Mod
});
var c=new Col();
c.add({},{init:true});
console.log(c.length);
A Fiddle: http://jsfiddle.net/jZeYB/
Warning : it may break things down the line.
Do you need to add the model to the collection right away? I presume that validation fails because you add it to the collection immediately.
Instead, when the button is pressed you could just create the view and blank model. When the model validates you add it to the collection. You would need a submit button/mechanism on the new row to add it to the collection (which invokes validation automatically).

Resources