I'm in the middle of a process where I'm retrieving data from a REST API for use in my Backbone.js application. At this point I don't need to do any manipulations to the data except for the fact that the data returned is an array. However, the future might bring such requests, so I've already created a Backbone model and collection for this type of data.
I've read that you could map your Array data into an object inside your Backbone collection, but I'm wondering since I already have a model, if it would be better practise to already map each element inside my Backbone model.
Since I'm not an expert in the Backbone.js framework, any links with more documentation about this section would be greatly appreciated.
UPDATE: I was actually looking for the parse method that is provided by the BackboneJS framework. By transforming the Array into an Object in the parse function I was able to solve the question.
You can use the parse method to parse any kind of transformation you'd like to do, like e.g. copying attributes, modifying attributes etc.
More information : http://backbonejs.org/#Collection-parse
Just as in the question you mentioned, this can achieved using parse, either on the Collection or the Model.
var UserModel = Backbone.Model.extend({
// String name is mapped to an object with the name property
parse: function(name) {
return {
name: name
};
}
});
var UserCollection = Backbone.Collection.extend({
model: UserModel
});
var collection = new UserCollection(['Ann', 'Joe', 'Jim', 'Bob'], {parse: true});
console.log(collection.at(0).get('name'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
In the example above, the collection is instantiated with data, in this case, parse doesn't run by default, so it needs to be set in the options, however if the collection normally gets its data from fetch, this will by default always parse.
Here is what I am trying to understand.
Often times I find myself writing backbone like this:
var CallModel = Backbone.Model.extend({
});
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
It is a very basic example but as you can see, there is nothing really in the model all the data is coming into the Collection via an external url call to a json file that is build from a database.
So whats the purpose of the model? I am sure that I am probably not using backbone.js to its fullest extent which is why I am here asking you guys.
First of all, "there is nothing really in the model all the data is coming into the Collection via an external url call" - this is not true.
Let's assume you've the following:
//Model
var CallModel = Backbone.Model.extend({
defaults: {
cost:0,
duration:0
}
});
(without custom attributes or methods, there is no point in extending the original Backbone.Model)
//Collection
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json'
});
And the json data returned from service, probably something like:
//Response
{
callSummary: {
missed: 2,
received: 3,
totalCalls:5
totalDuration: 20
}
calls: [{
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
},{
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
},{
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}],
//and more additional information from your db
}
Now you populate your collection with data by calling it's fetch method:
CallsCollection.fetch();
Your collection should look something like:
{
models: [{
attributes: {
callSummary: {},
calls: [{},{},{}],
...
},
...
}],
length:1,
url: "url/to/external/json",
...
}
The data will be added to a model's attribute hash. If you don't specify a particular model, as Bart mentioned in his answer, backbone will populate the collection with a Backbone.Model instance: Which is still not much useful - Wew... A collection with single model having entire response data inside it's attributes as it is...
At this point, you're wondering why did I even bother creating a model, and then a collection..?
The problem here is Collections are derived from Arrays, while Models are derived from Objects. In this case, our root data structure is an Object (not an Array), so our collection tried to parse the returned data directly into a single model.
What we really want is for our collection to populate its models from the "calls" property of the service response. To address this, we simply add a parse method onto our collection:
var CallsCollection = Backbone.Collection.extend({
model: CallModel,
url: 'url/to/external/json',
parse: function(response){
/*save the rest of data to corresponding attributes here*/
return response.calls; // this will be used to populate models array
}
});
Now your collection will be something like the following:
{
models: [{
...
attributes: {
...
id:001,
caller:"Mr.A",
callee:"Mr.B",
cost:1,
duration:5
}
},{
...
attributes: {
...
id:002,
caller:"Mr.X",
callee:"Mrs.Y",
cost:1,
duration:7
}
},{
...
attributes: {
...
id:003,
caller:"Mr.A",
callee:"Mrs.B",
cost:1,
duration:8
}
}],
length:3,
url: "url/to/external/json",
...
}
This - is what we want! : Now it is very easy to handle the data: You can make use of the add, remove, find, reset and handful of other collection methods effectively.
You can pass this models array into your templating library of choice, probably with two way bindings: When the respective view for one of the call model changes, the particular model will be updated, events will propagate from your models to the collection, and the particular model will be passed into the handler functions.
You can now call fetch, save, destroy, clear and a lot of other methods with ease on single unit's of data (each model), rather than hurdle with the entire data saved in a single model - which is pretty much useless, you've to iterate through the response data manually and perform CRUD and similar operations by your own, and in most cases: re-render the entire collection view. which is very, very bad and totally unmaintainable.
To conclude: If your data source doesn't return an array of objects, or you don't parse the response and return an array of objects from which n number of models are to be populated - Then defining a collection is pretty much useless.
Hopefully, now you get the idea.
Very helpful source of info:
Backbone, The Primer: Models and Collections
Developing Backbone.js Applications
backbonejs.org
You don't need to specify a model. A Backbone collection will default to using Backbone.Model if you don't specify this option. The following would work equally well if you don't need the models of the collection to be of a particular instance.
var CallsCollection = Backbone.Collection.extend({
url: 'url/to/external/json'
});
Reference
EDIT
In essence, specifying the model option within a collection is just a way to ensure that objects added to this collection will be instances of that particular model class. If the models being added to your collection don't have any custom behaviour outside of what is available to Backbone.Model, you don't need to create and specify a model as Backbone collections will default to using an instance of Backbone.Model as I have already mentioned. If, however, you wanted to ensure that models added to a particular collection were of a particular type and shared customized behaviour (e.g. validations, defaults, etc.), you would create your own model class by extending Backbone.Model and specifying this in the collection. I hope this clears things up for you.
Sounds Weird but this is the way.
Every collection in backbone, must represent a model, so basically a collections is a list of models.
Even if your model has no data, you need to indicate it when you create a Collection.
This is how backbone works for collections.
Is there a clean way to change the model of a Backbone collection at runtime? In other words, I have a collection called BaseCollection where by default its model is called BaseModel. The model acts as a base class for other models. Say AModel, BModel, etc.
Now during runtime the collection, based on specific params, needs to understand if it have to call the parse method (with a specific override) of the BaseModel or one of the parse methods used in models that extend from BaseModel.
Normally, this could achieved simply extending the collection each time I instantiate it. So, for example, by default is the defined as follow.
var BaseCollection = Backbone.Collection.extend({
model : BaseModel,
// other stuff here
});
Now if I want to have a collection where AModel has to be the model
BaseCollection.extend( { model: AModel } );
Here the complicated stuff. What if the collection does not contain model of the same type. In other words, what if the BaseCollection contains AModels, BModels, etc? Note that I need to call the parse method for each model, since they differ a little bit.
The magic of creating a collection of different models is by implementing a model function.
here is the examples from http://backbonejs.org/#Collection-model
var Library = Backbone.Collection.extend({
model: function(attrs, options) {
if (condition) {
return new PublicDocument(attrs, options);
} else {
return new PrivateDocument(attrs, options);
}
}
});
in your case you should use the attributes to decide which model you want to create
Trying to populate a Collection from a list of values, I am getting an error about the Collection's model's prototype being undefined. Looking at this question about a similar problem, I have checked that the Model is actually created before the collection is instanced, to the best of my ability.
The error is being thrown in one of the event handlers of the Marionette CompositeView that holds the Collection, after fetching the data from the server and trying to reset the collection with the list of values from the data which should be populated into it.
Note: Using Backbone 0.9.10
The Model
MyItemModel = Backbone.Model.extend({});
The Collection
MyCollection = Backbone.Collection.extend({
model: MyItemModel
});
The CompositeView's relevant code
MyCompositeView = Backbone.Marionette.CompositeView.extend({
initialize: function(options) {
_.bindAll(this);
this.model = new MyCompositeViewModel();
this.collection = new MyCollection();
},
//This event handler gets properly fired and run.
on_event: function() {
var that = this;
// The data comes through fine with this `fetch`
this.model.fetch({success: function() {
var collection_results= that.model.get("collection_results");
// The error fires in this line
that.collection.reset(collection_results);
that.render();
});
}
})
The error
The error happens in the add function in Backbone, when doing a get for the model object, checking to see if it is a duplicate. The failing code is here:
// Get a model from the set by id.
get: function(obj) {
if (obj == null) return void 0;
// The error originates from this line
this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
return this._byId[obj.id || obj.cid || obj[this._idAttr] || obj];
},
this._idAttr || (this._idAttr = this.model.prototype.idAttribute);
Here, the this.model.prototype.idAttribute fails because the prototype for the model is not defined.
Why is this happening, and how can it be fixed?
Thanks a lot!
The reason is, in Babkbone 0.9.10, if you call collection.reset(models) without options, the models will be passed to collection.add() which strictly needs real models as argument.
But, in fact, the arguments you passed are not real models. They are just an array of hash attributes.
Two options to fix:
Option 1: Call the reset with a parse option
that.collection.reset(collection_results, {parse: true});
Then reset will parse the array of hashes and set them as model.
Option 2: Upgrade to latest version Backbone 1.1.0.
Here reset() no longer pass responsibility to add() but use set() smartly. This option is recommended. And you don't need options here.
that.collection.reset(collection_results)
Another point
May I suggest you not to define model in CompositeView? CompositeView is for collection, not model. Of course I understand the model here is just to hold and fetch some data, but it would be really confusing for the code to be read by another developer, as well as your own maintaining.
To get bootstrapped data, you can load the data at first request and use conventional way to put it into collection. http://backbonejs.org/#FAQ-bootstrap
I have a model for my sources and a model for each segment. Each source has many segments. Each segment has an action that toggles an isSelected property.
I need to keep an updated list of selected segments. My first thought was to do something like...
App.Source = Ember.Object.extend({
selectedSourceList = Em.A(),
selectedSourcesObserver: function() {
// code to update array selectedSourceList
}.observes('segment.isSelected')
});
.. but that observes() function isn't right. I'm new to ember, so my approach may be completely wrong.
How can a method in one model observe the properties of many other models?
EDIT: corrected names to indicate that the segment model is for a single segment - not a collection of segments (that is what I plan to do in the sources model).
I think there are three parts to your question:
how to observe a collection
observers in general
managing relationships
observing a collection
The #each property helps observe properties for items in a collection: segments.#each.isSelected
observers in general
.observes() on a function is a shorthand to set up an observer function. If your goal for this function is to update a collection you might be better served by using .property() which sets up an observer and treats the function like a property:
selectedSegments: function() {
return this.get('segments').filterProperty('isSelected', true);
}.property('segments.#each.isSelected')
This means selectedSegments is the subset of segments from this object that are selected and is automatically managed as items are dropped or marked selected.
managing relationships
For plain Ember Objects you will need to manage the relationships, pushing new items into the array, etc.
segments = Em.A(), //somehow you are managing this collection, pushing segments into it
Also note the difference between Ember Objects and Ember Models. Ember Data is an optional additional library that allows specifying models and relationships and helps to manage the models for you. With Ember data you might have something like:
App.Source = DS.Model.extend({
//ember-data helps manage this relationship
// the 'segment' parameter refers to App.Segment
segments: DS.hasMany('segments'),
selectedSegments: function() {
return this.get('segments').filterProperty('isSelected', true);
}.property('segments.#each.isSelected')
});
And App.Semgent
App.Segment = DS.Model.extend({
selection: DS.belongsTo('selection')
});