Can I create custom methods in Backbone.collections? - javascript

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());

Related

Backbone fetching data from model vs collection

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.

Backbone.js View, ParentView, Model, Collection and Template Best Practices

I am looking for some description of best practices for views and models/collections in Backbone. I know how to add models to collections, render views using templates and use views in parent views, however I'm looking for more context and perhaps some example links.
I've updated this question to be more specific.
Let's say you have a more grid layout with all kinds of variation, that gets pulled from the same collection. What would you do here to create this page? A simple child view repeated in a parent view won't work because the variation of the grid items is too great. Do you:
create tons of tiny views and collections and render all of these different views using the relevant collections into that one page?
create a complex template file that has a loop in it, that as you go through the loop, the loop outputs different markup?
Do people put multiple views inside a parent view, all from the same model?
Similarly, do people mix different models into the same parent view? For example movies and tv shows - these different models, can get they added to the same collection that renders that list?
Thanks!
You've asked good question. To answer it lets take a look to this from other angle:
On my exp i used to check first is there any logic on parent view, like sorting, validation, search and so on. Second - Collection with models or just model with array as property : is the model is independent and may exist without collection , for example you have navigation item, and there are no sense to make separate model for each item and navigation as collection as you will never use item itself. Another case you have user list. You may use user model in a lot of places and its better to make a separate model for user and collection to combine it.
Your case with UL may be resolved with single model and items properties with array of li, simple grid may have same approach as i don't see some special logic on wrap from your description.
But i should point out - i had close task to build mansory grid with collection parsed from server, items were models as it had different data structure, different templates and different events listener.
Taking decision i considered folowing:
item as independent tile, may be used as in grid and also independent.
item is model + template + view. different Models types helped to support different data structure, different Views types helped to support different events listeners and user interaction, different templates - diff looks.
collection as a tool to fetch initial data + load extra items + arrange mansonry view + create models according to fetched result.
UPDATE
Lets consider this pseudo code as masnonry implementation:
Models may looks like these:
var MansonryModel = Backbone.Model.extend({
/* Common methods and properties */
}),
MansonryVideoModel = MansonryModel.extend({
defaults: {
type: 'video',
videoUrl: '#',
thumbnail: 'no-cover.jpg'
}
}),
MansonryImageModel = MansonryModel.extend({
defaults: {
type: 'image',
pictureUrl: '#',
alt: 'no-pictire'
}
});
var MansonryCollection = Backbone.Collection.extend({
model: MansonryModel
});
Views could be like this:
var MansonryTileView = Marionette.ItemView.extend({
/* place to keep some common methods and properties */
}),
MansonryVideoTile = MansonryTileView.extend({
template: '#video-tile',
events: {
'click .play': 'playVideo'
},
playVideo: function(){}
}),
MansonryImageTile = MansonryTileView.extend({
template: '#image-tile',
events: {
'click .enlarge': 'enlargePicture'
},
enlargePicture: function(){}
});
var MansonryListView = Marionette.CollectionView.extend({
childView : MansonryItem
})
Hope this help

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!

Backbone.js nested object structure

I am working on a project where we have data models that look basically like this...
var library = {
location: 'Somewhere'
books: [
{
name: 'Good Book',
publisher: 'Great publisher'
authors: [
{
name: 'Joe Schmoe',
age: '65'
},
{
name: 'Robert Smith',
age: '47'
}
}
}
I am trying to figure out what the best way of laying out the model structure.
Something like...
var LibraryModel = Backbone.Model.extend({});
var Book = Backbone.Model.extend({});
var Books = Backbone.Collection.extend({
model: Book
});
var Author = Backbone.Model.extend({});
var Authors = Backbone.Collection.extend({
model: Author
});
Is this the best way to tackle this? Has anyone else faced anything like this?
Whether or not you need N to N, 1 to N or whatever relationships is largely a back-end concern, IMO. Unless you dealing strictly with CRUD screens, Backbone models typically don't translate directly to a back-end model, though they often approximate a back-end model.
That being said, Backbone-relational is definitely a good option for handling situations like this. It provides a very robust set of features for handling relational code and is worth looking in to.
If you'd rather stay clear of a large plugin and it's associated configuration and requirements, though, there are some simple tricks that you can use in your models directly. Since you know your data structure up front, you can make large assumptions about the code you need to write instead of having to create a very flexible system like BB-Relational.
For example, getting a list of book into a library, using the above data structure, can be as simple as:
Book = Backbone.Model.extend({});
Books = Backbone.Collection.extend({
model: Book
});
Library = Backbone.Model.extend({
initialize: function(){
this.parseBooks();
},
parseBooks: function(){
var data = this.get("books");
this.unset("books", {silent: true});
this.books = new Books(data);
}
});
I've used this code half a dozen times and it's worked out just fine for me every time. You can extend this same pattern and set of assumptions in to each layer of your object model.
You might also need to override the toJSON method on your model:
Library = Backbone.Model.extend({
// ... code from above, here
toJSON: function(){
var json = Backbone.Model.prototype.toJSON.call(this);
json.books = this.books.toJSON();
return json;
}
});
Depending on how far down this path you need to go, you will likely end up re-creating half of the functionality that BB-Relational already provides. It might be easier to use a plugin like that, with declarative relationship handling if you have a large enough structure.
IMO, it's worth understanding these basic patterns, knowing how to do this for yourself before you dig in to BB-Relational. But if you're constrained for time, I'd start with BB-Relational or some other relational mapper (I think there are a few more out there, these days).
i would not tackle this that way, because you have an N to N relationship,
1 book can have multiple authors
and 1 author has most likely written more than 1 book.
i would take a look at Backbone Relational plugin
it's not default backbone, but it does the trick the best way i can think off.
however if you only need a small solution
you can just have all your authors in a collection, and use a simple author ID array in your book model.

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