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's an example that uses Backbone with React.
He defines a Model: var _todos = new Backbone.Model();
And then adds two functions to it:
var TodoStore = _.extend(_todos, {
areAllComplete: function() {
return _.every(_todos.keys(), function(id){
return _todos.get(id).complete;
});
},
getAll: function() {
return _todos.toJSON();
}
});
What I don't understand is why areAllComplete is being applied to a Model instead of to a Collection.
Shouldn't this be a function in a Collection that will get all of its models and check that complete attribute.
Similarly, I would expect getAll to belong to a Collection - get all of its models.
This example seems to replace Collection with Model.
Maybe I don't totally understand how models are used.
That example is using Backbone.Model in a fairly wierd way in my opinion.
This is where it's adding new todos to the store:
var id = Date.now();
_todos.set(id, {
id: id,
complete: false,
text: text
});
}
What it's basically doing is setting every todo-item as an attribute of the Model, using the id as the attribute name. It ends up with _todos.attributes looking something like below
{
"1436600629317": {
"id": 1436600629317,
"complete": false,
"text": "foo"
},
"1436600629706": {
"id": 1436600629706,
"complete": false,
"text": "bar"
}
}
That's the same output you get from _todos.toJSON(). I've no idea why they decided to implement it like that, if they were to try using Backbone.Sync they'd end up with a server API that's not exactly RESTful. It seems strange to use Backbone without leveraging any of the things Backbone provides. There's a reference to the change event here but I don't see it being used anywhere. You could easily reimplement that store using any regular JS object.
The only thing that example seem to be actually using from Backbone is Backbone.Events in the dispatcher. You're totally right that using a Collection would make way more sense because then you could actually make it talk to a REST based server API. That example seems to only use Backbone for the sake of using Backbone.
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.
I am working (for the first time) with Backbone.js in creating a reasonably simple application. I am currently working with two entities that have a one-to-many relationship:
Project: has many groups
Group: belongs to one project
I looked at a few different ways of representing relationships between models, from Backbone's own recommendations to Ligament to Backbone-relational. However, I eventually settled on using simple function calls. For example, within my 'Project' model, there's a function called groups():
groups: function() {
return new Collections.Groups(groups.where({project_id: this.get('id')}));
}
And within the Group model:
project: function() {
return projects.where({id: this.get('project_id')})[0];
}
This allows me to do things like:
var foo = new Project();
var foo_groups = foo.groups();
foo_groups.at(0).get('name');
var bar = new Group();
var bar_project = bar.project();
bar_project.get('name');
To me, the tradeoff here is the overhead of maintaining up-to-date relationships (when using something like backbone-relational) versus the processing time used calling the 'groups()' function each time you want to get a project's groups.
Can anyone advise me on any pitfalls I may encounter down the road as I build out more relationships in this way, perhaps recommending a better solution (other than the extensions mentioned above)? The application will eventually have about 6 entities with a variety of one-to-many and many-to-many relationships
Project.groups
I have an implementation of what I call AutoUpdatedCollection.
I use it in the scenario you have a shared Collection which contains every Model of one class, and this Collection is used to populate sub-Collections those have to be updated when the shared Collection is updated.
Check the implementation of the Backbone AutoUpdatedCollection.
Following your example I suppose there is a common/shared Collection called window.groups, then I'll implement Project Model like this:
// code simplified and no tested
var Project = Backbone.Model.extend({
initialize: function(){
this.groups =
new App.AutoUpdatedCollection({
name: "Project-" + this.id + "-groups",
sourceCollection: window.groups,
filterField: "project_id",
filterValue: this.id
}).filteredCollection;
this.groups.on( "add", this.addGroup, this );
this.groups.on( "remove", this.removeGroup, this );
}
});
Maybe is not as simple as this, if you have any problem let me know.
Group.project
I'd just initialize the Group.project attribute in the Group.initialize():
var Group = Backbone.Model.extend({
this.project = window.projects.where({id: this.get('project_id')})[0];
});
i have this result from the restful service i have:
NOTE: Response is in JSON format it's the plugin from chrome which displays it like that.
if you look at image two [the one above this] the models attributes is Items then each item are under Items. What should i do to access item?
my problem is i can't access or retrieve data of each item from this result. i must not change anything from the server side though. I'm using backbone with this code.
window.Item = Backbone.Model.extend();
window.ItemCollection = Backbone.Collection.extend({
model: Item,
url: 'http://localhost/InterprisePOS/Product/loaditembycategory/Event Materials'
});
window.ItemListView = Backbone.View.extend({
tagName : 'ul',
initialize: function(){
this.model.bind("reset",this.render,this);
},
render: function(eventName){
_.each(this.model.models.Items, function(item){
$(this.el).append(new ItemListItemView({model:item}).render.el);
},this);
return this;
}
});
window.ItemListItemView = Backbone.View.extend({
template : _.template($("#item-list").html()),
render: function(eventName){
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
var AppRouter = Backbone.Router.extend({
routes:{
"":"list"
},
list:function(){
this.itemList = new ItemCollection();
this.itemListView = new ItemListView({model:this.itemList});
this.itemList.fetch();
$("#itemContainer").html(this.itemListView.render().el);
}
});
var app = new AppRouter();
Backbone.history.start();
UPDATE
I was able to correct the my problem with nested json objects. Now the Models attrib or my Collection is populated with individual items. But still the problems is it doesn't work and doesn't display my views.
This is the code i added:
parse: function(response) {
return response.Items;
}
UPDATE
I Finally answered my question! horray! somehow i forgot to put "()" on render in my ItemListview. and also $("#ItemContainer") doesn't seem to work so i made it to $('#ItemContainer) now i'm displaying the Details from my model.
I'm fairly certain that Backbone by default uses JSON for all requests and has no idea what to do with XML, you'll probably need to override the sync method for the collection in order to us use XML.
The following should be helpful in addressing your issue: http://newcome.wordpress.com/2011/02/20/consuming-xml-web-services-in-backbone-js-using-jath/
They use a 3rd party library xml parser in the sync parse operation which converts the model to JSON which Backbone can use natively.
Make sure the response is returned as JSON. Backbone works on JSON data by default.