Backbone fetching data from model vs collection - javascript

I created a simple backbone project, where it fetches all books details and show it in UI. I am fetching all the books details from the model. not at all using collection something like this
var BookModel= Backbone.Model.extend({
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
This is working fine. But why do I have to use collections ? If I would have used collection it would be something like as follows
var BookModel= Backbone.Model.extend({
defaults:{
id:'',
name: '',
author: ''
}
});
var BookCollection= Backbone.Collection.extend({
model: BookModel,
initialize: function(){
this.fetchData();
},
fetchData: function(){
this.url= "/get/all_books";
this.fetch({
success: success_callback,
error: error_callback
})
}
});
The same effect. I don't understand why to use Collections in my case. please help me to understand this concept with my example why do I have to use collections here. I Googled a lot and could find the best answer.
Thanks

Imagine that you have two 2 routes:
/books
/books/:id
Now for getting a specific book you can send a request to /book/:id route, where :id is the id of the book.
GET /books/1
< { id: 1, title: 'On the Genealogy of Morality', ... }
Now what happens if you want to get all the books? You send a request to /books route.
GET /books
< [{ id: 1, title: '...', ... }, { id: 2, title: '...', ... }, ...]
Backbone follows the same principle. Model for a single book. Collection for many books. When you use a collection, Backbone creates one Model for each book. Using a Model for more than one item is wrong.
You said "Backbone creates one Model for each book.". at what step it creates?
It creates the models on the sync event, i.e. when the request for getting all the items is complete.
...how does it helps me. In my case I always fetch all books, not single book.
Backbone Collections always use Backbone Models. If you don't set the model property of the Collection explicitly, Backbone uses a normal Model, i.e. the model property of a Collection should always refer to a Model.
// https://github.com/jashkenas/backbone/blob/master/backbone.js#L782
// Define the Collection's inheritable methods.
_.extend(Collection.prototype, Events, {
// The default model for a collection is just a **Backbone.Model**.
// This should be overridden in most cases.
model: Model,
Consider a Model as an object and a Collection as an array of objects.

More than anything, dividing your data into logical units (models) and grouping them into categories (collections) make it easy for you to reason about, manipulate and change your data. Once you build something that is even just a tiny bit more complex than what you have built, this becomes a priority. The point isn't that you are getting some magic functionality that you couldn't otherwise get. It's all javascript after all. The point is that models and collections provide data-structuring that is helpful when building dynamic applications. In fact that's the whole point of backbone and MV* in general, to provide helpful tools and abstractions. Collections are a solution to a whole host of problems that you will run into later down the road, when you add even the tiniest bit of extra complexity to your app.
So, you ask why you have to use collections and I guess the answer that you already knew is, you don't have to use collections. In fact, it sounds like you don't need to use an MV* library at all.

Related

How do I pass references of views/models to other views/models/routes in backbone.js

So I'm really new to backbone.js and I'm trying to understand the basic concept when it comes to getting parts(views/models/routes) to interact with other parts.
Here's an example. I have a 'screen' model object being rendered by a 'singleScreen' View to the page. I also have a 'sidebar' model and view being rendered. When i click on a link in the sidebar i want it to render a different screen model object and alter some of the HTML in a separate html div (the heading) according to the 'name' attribute i gave my screen model.
So first question, should all of the code for re-rendering a different view and changing the heading html be done in the routes.js file? It seems like that file would get pretty big pretty fast. If so how do i get a reference to the model object i want to render in the routes.js file so i can access things like myModel.name (which is instantiated in the app.js file)?
Do I handle the browser history and rendering of my view as separate things and add code for 'link is clicked, render this' functionality in my app.js file (the file where I instantiate all my objects)? If that's the case how does my app know what to render if a user tries to go directly to a view by typing in the URL, rather than clicking?
OR, and this is the most likely scenario as far as i can tell,
Do i use the initialize functions of my models/views to trigger/listenTo an event for when a link is clicked(or backbone.history() is changed?) and call render?
I've messed around with all 3 approaches but couldn't understand how to pass references of objects to other parts of the app, without just making those objects global variables (which feels so wrong).
For the last scenario, I messed around with events but everywhere I've read says you have to include a reference to the object that it's listening too, which seems to defeat the whole purpose of setting up an events object and listening for an event rather than just querying the state of a variable of that object...
eg.
this.listenTo(sidebarModel , "change:selected", this.render());
How do i pass a reference to sidebarModel object to the singleScreen view for it to know what it's meant to be listening to.
I'm not really looking for a coded answer, more so just an understanding of best practices and how things are meant to be done.I feel like I'm close but I know i'm missing/not understanding something which is why I'm not able to figure this out myself, so a little enlightening on the whole topic would be greatly appreciated. Thanks.
First, you need to understand the role of each Backbone classes. Reading on MVC first might help.
Model
The model isn't necessary when rendering a view. Its role is to handle the data, which can be local, or from an API. All the functions that affect the data should be in the model, and a model shouldn't have anything related to rendering.
There are always exception, where you could use a model to handle data related only to rendering, but you'll know when you find such case.
A simple example is a book:
// The Book model class
var Book = Backbone.Model.extend({
idAttribute: 'code',
urlRoot: '/api/book'
});
// a Book instance
var solaris = new Book({ code: "1083-lem-solaris" });
Fetching from an API would call:
// GET http://example.com/api/book/1083-lem-solaris
solaris.fetch(); // this is async
When fetching, the API returns the JSON encoded data.
{
"code": "1083-lem-solaris",
"title": "Test title",
}
The attributes are merged with the existing attributes, adding the ones that are not there yet, and overwriting the values of the ones already there.
Collection
A collection's role is to manage an array of models, which, again, can be local or fetched from an API. It should contain only functions related to managing the collection.
var Library = Backbone.Collection.extend({
model: Book,
url: '/api/book'
});
var myLibrary = new Library();
// just adds an existing book to our array
myLibrary.add(solaris);
You can fetch a collection to get an array of existing books from an API:
myLibrary.fetch();
The API should return:
[
{ "code": "blah-code-123", "title": "Test title" },
{ "code": "other-code-321", "title": "Other book title" }
]
Using the collection to create a new book and sync with the API:
var myNewBook = myLibrary.create({ title: "my new book" });
This will send a POST request with the attributes and the API should return:
{ "code": "new-code-123", "title": "my new book" },
View
The view handles its root DOM element. It should handle events from its DOM. It's best used to wrap small component and build bigger components from smaller components.
Put links directly in the templates, in the href of an anchor tag. There's no need to use events for that.
link`
Here's how I render (simplified) a list.
// book list item
var BookView = Backbone.View.extend({
tagName: 'li',
template: _.template('<%= title %>'),
initialize: function() {
// when the model is removed from the collection, remove the view.
this.listenTo(this.model, 'remove', this.remove);
},
render: function() {
this.$el.empty().append(this.template(this.model.toJSON()));
return this;
}
});
// book list
var LibraryView = Backbone.View.extend({
template: '<button type="button" class="lib-button">button</button><ul class="list"></ul>',
events: {
'click .lib-button': 'onLibButtonClick'
},
initialize: function(options) {
this.listenTo(this.collection, 'add', this.renderBook);
},
render: function() {
// this is a little more optimised than 'html()'
this.$el.empty().append(this.template);
// caching the list ul jQuery object
this.$list = this.$('.list');
this.collection.each(this.renderBook, this);
return this; // Chaining view calls, good convention http://backbonejs.org/#View-render
},
addItem: function(model) {
// Passing the model to the Book view
var view = new BookView({
model: model
});
this.$list.append(view.render().el);
},
onLibButtonClick: function() {
// whatever
}
});
Router
The router handle routes and should be as simple as possible to avoid it getting too big too fast. It can fetch collections and models, or the view can handle that, it's a matter of pattern at that point.
var LibraryRouter = Backbone.Router.extend({
routes: {
'*index': 'index',
'book/:code': 'bookRoute',
},
index: function() {
var library = new LibraryView({
el: 'body',
// hard-coded collection as a simple example
collection: new Library([
{ "code": "blah-code-123", "title": "Test title" },
{ "code": "other-code-321", "title": "Other book title" }
])
});
library.render();
},
bookRoute: function(code) {
var model = new Book({ code: code });
// here, I assume an API is available and I fetch the data
model.fetch({
success: function() {
var view = new BookPageView({
el: 'body',
model: model
});
view.render();
}
});
}
});
Events
Everything in Backbone has the Events mixin, even the global Backbone object. So every class can use listenTo to bind callbacks to events.
It would be very long to go in depth for everything in Backbone, so ask questions and I'll try to extend my answer.

Can I create custom methods in Backbone.collections?

Apologies I'm still learning the fundamentals of Backbone.
I'm a little confused about Backbone.collections. I understand that they consist of a group of collections, and can have url and model attributes. Can you define a custom method in a backbone.collection?
For example:
var Books = Backbone.Collection.extend({
url: '/books',
model: Book,
getBook: function(id, options){
......
}
});
Is this possible and if so, what would it do?
Thanks!
This is absolutely possible and a great way to encapsulate functionality specific to that collection. Although typically in practice I find myself expanding the functionality of the Models more.
As far as what it would do... If you had a collection of books you may want to write helper methods for accessing, pruning, or serializing your collection. I'm not sure what your business needs are.
Have look at underscore.js for available collection functions, so you don't reinvent the wheel.
Custom methods on collections would usually do one of two things:
Handle events fired by the models, if you need to trigger custom actions when that happens
Hold reusable looping/filtering/sorting so that you don't have to inline that stuff throughout your application
When I say collections would usually do this, it doesn't necessarily mean that you're doing anything wrong if your methods falls outside of these two categories, this is just to get an idea what kind of logic you might want to put into a collection custom method.
Take your books for instance, let's say you wanted to have a custom method that returns the total number of pages for all of the books in your collection. That could look something like this:
var Books = Backbone.Collection.extend({
url: '/books',
model: Book,
getTotalNumPages: function() {
var numPages = 0;
this.each(function(model) {
numPages += parseInt(model.get("pageCount"), 10);
});
return numPages;
}
});
// Elsewhere in your app
console.log("Total pages: ", books.getTotalNumPages());

Optimal URL structure for Backbone.js and Backbone implementation

I'm developing a RESTful API for a Quiz app, which is going to be built with Backbone.js and Marionette. I'm quite new to backbone and was wondering what de best URL structure would be. I have the following resources:
Answer,
Question which contains Answers,
Question Group which contains Questions,
Quiz which contains Question Groups.
Two possible URL structures come to mind:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /quizzes/:id/questiongroups/:id
GET /quizzes/:id/questiongroups/:id/questions
GET /quizzes/:id/questiongroups/:id/questions/:id
GET /quizzes/:id/questiongroups/:id/questions/:id/answers
or:
GET /quizzes/:id
GET /quizzes/:id/questiongroups
GET /questiongroups/:id
GET /questiongroups/:id/questions
...
Now, I have been trying to use both of these options. With the first one, I can't figure out how to define the collections as a property of the parent models in Backbone so that I can use fetch() on them. The problem with the second option is a bit different: as I understand it, Backbone derives the url for a model from its collection, but the collection is a child of another resource, whereas the url for getting a single resource uses another collection, namely the global set of resources.
I'm pretty sure I'd have to override url() in both cases. I tried some things but didn't come up with anything useable at all. Also, I'd rather not override every single url()-model in the app, changing the API structure to suit the preferences of Backbone seems like a better option to me.
Any pointers as to what seems the right way to do it with Backbone would be great!
Thanks
If questiongroups can only appear in a single quiz, then the first option (the hierarchical one) is an obvious choice. To comply with RESTful conventions, you might want to consider using singular nouns instead: /quiz/:id/questiongroups/:id/question/:id/answer/:id
To solve your fetching problem, I would recommend using nested backbone models as per this answer: https://stackoverflow.com/a/9904874/1941552. I've also added a cheeky little parentModel attribute.
For example, your QuizModel could look something like this:
var Quiz = Backbone.Model.extend({
urlRoot: '/quiz/', // backbone appends the id automatically :)
defaults: {
title: 'My Quiz'
description: 'A quiz containing some question groups.'
},
model: {
questionGroups: QuestionGroups,
},
parse: function(response){
for(var key in this.model){
var embeddedClass = this.model[key];
var embeddedData = response[key];
response[key] = new embeddedClass(embeddedData, {
parse:true,
parentModel:this
});
}
return response;
}
});
Then, your QuestionGroups model could have the following url() function:
var QuestionGroups = Backbone.Model.extend({
// store metadata and each individual question group
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
Alternatively, if you don't need to store any metadata, you could use a Backbone.Collection:
var QuestionGroups = Backbone.Collection.extend({
model: QuestionGroup,
url: function() {
return this.parentModel.url()+'/questiongroup/'+this.id;
}
});
I'm afraid I haven't tested any of this, but I hope it can be useful anyway!

Is it okay to delete attributes in my Backbone.Model's initialize method, and change them to properties of the model?

I have the following object relations between my three models (I am not using Backbone-relational... this is just describing the underlying structure of my data) :
Person has many Cars
Car has many Appraisals.
I have a single method to retrieve a Person, which brings along all the Cars and the Appraisals of those Cars. It looks like this:
{id: 1,
name: John Doe,
cars: [
{id: 3, make: 'Porsche',
appraisals: [
{id: 27, amount: '45000', date: '01/01/2011'}
]
},
{id: 4, make: 'Buick', appraisals: []}
]
}
When I create a new Person I pass in this entire mess. In my Person's initialize function I do this:
...
initialize: function() {
//Cars => Collection of Car
this.cars = new Cars();
_.each(this.get('cars'), function(car) {
this.cars.add(new Car(car));
});
this.unset('cars');
}
...
And in my Car initialize function I do something similar:
...
initialize: function() {
//Appraisals => Collection of Appraisal
this.appraisals = new Appraisals();
_.each(this.get('appraisals'), function(appraisal) {
this.appraisals.add(new Appraisal(appraisal));
});
this.unset('appraisals');
}
...
I also have to override the toJSON function for Person and Car models.
Is there anything wrong with this? I've seen it suggested elsewhere to make nested collections properties rather than attributes, and my experience confirms that is easier, but I'm wondering if I am breaking some rules or doing something dumb here.
I don't have the answer for 'storing nested collections as properties or as attributes' question, but I think you can simplify your code a bit initializing nested collections like this:
Person:
...
initialize: function() {
this.cars = new Cars(this.get('cars'));
this.unset('cars');
}
...
Car:
...
initialize: function() {
this.appraisals = new Appraisals(this.get('appraisals'));
this.unset('appraisals');
}
...
I answered a similar question here: backbone.js - getting extra data along with the request
In the answer that I provided, it was more about a collection owning a model association — a has one, basically.
I think a Person should have a CarsList containing Car models. A Car should have an AppraisalsList containing Appraisal models. You would probably override the parse and toJSON functions of Person and Car as needed.
I would definitely avoid using attributes for associations. The unset functions in the above examples are a bad smell to me.
If I may give my 2 cents worth of input(s):
If you were to draw an OOD class diagram of the classes and model the associations in any object-oriented language of your choice (other than javascript) how would you do it?
You see backbone.js helps put 'structure' to your javascript that could become an tangled spaghetti code. So if you Person has many Cars and a Car has many Appraisals you have two options: Compositions vs. Associations
Composition: What you are doing above: A person object is responsible for creating the cars and car objects for creating appraisals. The 'lifetime' of each object is dependent on the parent. Now that may/may not be how it 'should' be modeled, but that's the choice you've made.
Now, let's see simple associations. You create the person, cars, and appraisals independently (probably appraisal cannot exist without the car, but let's assume otherwise for now).
Now these objects are created but you need to "wire up" these associations - you can do that externally in a separate "initializer" class/container so to speak and just use setter/getters to connect them.
Conclusion: Use what best models your domain and don't let it be governed by your data store (i.e., the JSON object in this case). Backbone's sheer beauty comes from this ability of imparting classic OO structure to your code and thinking in that way when coding. So choose a good mix of OO relations (compositions, aggregations or simple associations) and select the 'best model' for your problem and implement accordingly.
Combining with #kulesa's suggestion, you'll "clean up" your code and achieve exactly what you want without worrying about breaking any principles/practices while organizing your code effectively.
Hope this helps!
I don’t personally think it makes sense to use properties to store some of a model’s data. What experiences did you have that made properties feel easier?
Backbone, internally, appears to use properties only for metadata (e.g. the by-id and by-cid maps of the models in a collection) and quick access to attributes (e.g. the id property, which is updated whenever the id attribute changes). Using properties also stops you from using Backbone’s event system or .get()/.set(), and forces you to override .toJSON().
Sticking with attributes, I believe that you could get the same result by overriding .set() — It gets called when a new instance of a model is created, before .initialize(), and it will also be called if something else tries to set any attribtues. I once did something similar like this:
var Person = Backbone.Model.extend({
set: function(attributes, options){
var outAttributes = {};
_.each(attributes, function(value, name){
switch(name){
case 'cars':
outAttributes.cars = new Cars(value);
break;
default:
outAttributes[name] = value;
}
}, this);
Backbone.Model.prototype.set.call(this, outAttributes, options);
}
});
…you could modify it to, say, update an existing Cars instance instead of creating a new one.
.set() is also where Backbone updates the value of the id property, so if you choose to use properties instead of attributes, it might still be the best to suck in the values (instead of .initialize()).
I had a similar situation and I solved it this way:
parse: function(data) {
if (data.Success) {
var policies = Rens.get('Policies').model, // model with nested collextion
claims = Rens.get('Claims').model; // model with nested collextion
// reseting collections
claims.claims.reset();
policies.policies.reset();
$(data.Result.Policies).each(function(i, policy) {
var claimsList = policy.ClaimsList,
policyWithClaims = _.clone(policy);
claims.claims.add(claimsList);
_.extend(policyWithClaims, {
ClaimsList: claims.getPolicyClaims.bind({
context: claims,
policyNumber: policy.PolicyNumber
}),
CarYearString: Rens.formatDate(policy.CarYear).date.HH,
PolicyEndDateString: Rens.formatDate(policy.PolicyEndDate).fullDate
});
policies.policies.add(policyWithClaims);
});
}
}
After this i have collection with policies and each policy has attribute with method linked to claims collection.
claims.getPolicyClaims returns all claims for current policy

Persisting & loading metadata in a backbone.js collection

I have a situation using backbone.js where I have a collection of models, and some additional information about the models. For example, imagine that I'm returning a list of amounts: they have a quantity associated with each model. Assume now that the unit for each of the amounts is always the same: say quarts. Then the json object I get back from my service might be something like:
{
dataPoints: [
{quantity: 5 },
{quantity: 10 },
...
],
unit : quarts
}
Now backbone collections have no real mechanism for natively associating this meta-data with the collection, but it was suggested to me in this question: Setting attributes on a collection - backbone js that I can extend the collection with a .meta(property, [value]) style function - which is a great solution. However, naturally it follows that we'd like to be able to cleanly retrieve this data from a json response like the one we have above.
Backbone.js gives us the parse(response) function, which allows us to specify where to extract the collection's list of models from in combination with the url attribute. There is no way that I'm aware of, however, to make a more intelligent function without overloading fetch() which would remove the partial functionality that is already available.
My question is this: is there a better option than overloading fetch() (and trying it to call it's superclass implementation) to achieve what I want to achieve?
Thanks
Personally, I would wrap the Collection inside another Model, and then override parse, like so:
var DataPointsCollection = Backbone.Collection.extend({ /* etc etc */ });
var CollectionContainer = Backbone.Model.extend({
defaults: {
dataPoints: new DataPointsCollection(),
unit: "quarts"
},
parse: function(obj) {
// update the inner collection
this.get("dataPoints").refresh(obj.dataPoints);
// this mightn't be necessary
delete obj.dataPoints;
return obj;
}
});
The Collection.refresh() call updates the model with new values. Passing in a custom meta value to the Collection as previously suggested might stop you from being able to bind to those meta values.
This meta data does not belong on the collection. It belongs in the name or some other descriptor of the code. Your code should declaratively know that the collection it has is only full of quartz elements. It already does since the url points to quartz elements.
var quartzCollection = new FooCollection();
quartzCollection.url = quartzurl;
quartzCollection.fetch();
If you really need to get this data why don't you just call
_.uniq(quartzCollecion.pluck("unit"))[0];

Categories