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
Related
I have a backbone viev which generates a list item view. In the render function it goes through the collection and generates each of the sub item views with some standard code as follows:
render: function () {
_(this.collection.models).each(function(item){
$this.appendItem(item);
}, this);
},
I would like to know how to access a specific view from the item list, say at position 0 or something. I want to be able to trigger a function from the item list view for that specific item.
Well, that depends on how the appendItem function is implemented, I guess is in there where you build the SubViews.
You can store each created SubView in an Array so the Array will offer you a way to manipulate the SubViews.
But if I can offer a piece of advice I would say that this is a code smell from the begining. Instead of trying to manipulate a concrete SubView you can manipulate the Model which is associated with the SubView and make the SubView to be listening to this change.
Then you will start to be thinking in manipulating Models instead of Views.
I have a collection of items they all share some data (like an id, title) however outside of their shared stem of attributes they're functionally unique items and have separate views and business logic.
My problem is without prior experience in Backbone style MVC, I don't know the pros / cons of each... or perhaps if there is a much more elegant solution I'm missing. Here's an example of the 3 techniques I could potentially use?
var gizmoCollection = new Backbone.Collection(); // or extend
var gizmoModel = Backbone.Model.extend({ ... });
var morgView = Backbone.View.extend({ ... });
var blarView = Backbone.View.extend({ ... });
// 1.) Create an attribute for the view in the model?
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1': view: morgView }));
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2': view: blarView }));
// 2.) Or create a seperate model for each type of model?
var morgModel = morgModel.extend({});
var blarModel = blarModel.extend({});
gizmoCollection.add(new morgModel({ title: 'Gizmo1' });
gizmoCollection.add(new blarModel({ title: 'Gizmo2' });
// 3. Or register 'types' of views?
gizmoView.subClassView('morg', morgView);
gizmoView.subClassView('blar', blarView);
gizmoCollection.add(new gizmoModel({ title: 'Gizmo1', type: 'morg' });
gizmoCollection.add(new gizmoModel({ title: 'Gizmo2', type: 'blar' });
My choice would be to create separate models and it views if necessary. The reason is that each model should hold business logic for it self. Now, you may find sometimes easier to do this just with subviews if there is only presentational logic which is different for each model type or model attribute value.
You should keep in mind following:
Presentational logic goes to Presenter(s) (Backbone.View)
Business logic goest to model(s) (Backbone.Model)
Navigation logic either router (aka controller) or you can make your Event Bus from Backbone.Events or jQuery.callbacks() which will do this job and probably some other things which you want separate from your presenters and models.
Final note. Always keep in mind that your app will grow, sometimes it is wiser to add few more lines of code regardless you don't need so much complexity at the moment. But if you senses tell you that at some point that code will become more complex, well you should do it right away or later you will not have enough time.
Do I need to bind to every collection that is instantiated of the same type or do I bind to a common change event that passes in a reference to itself?
example: What would an interface with 5 different todo lists look like? Would they each need a unique id? I'm guessing they would be placed into another collection of todo lists? Any code examples would be great. Thanks.
Edit: Sorry if I'm still not clear.
TodoList is a collection of Todo models.
My app needs to display any given number of TodoList's on it. What is the best way to organize different instances of these?
generally speaking, you don't need to do anything special to handle multiple instances of a collection.
MyCollection = Backbone.Collection.extend({ ... });
MyView = Backbone.View.extend({ ... });
c1 = new MyCollection();
c2 = new MyCollection();
v1 = new MyView({collection: c1});
v2 = new MyView({collection: c2});
each view will only reference it's collection, and there is no need to worry about data being mis-matched between the two collections. when you add a model to one, it won't show up in the other. when you manipulate the view for one, it won't manipulate the view for the other (assuming you've written your view correctly).
you'll need a to coordinate how each view for each collection is displayed on the screen. this would work like any collection / item view setup, where you have a collection view that instantiates each child view, renders the child view, and then displays the child view's el within it's own el.
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];
I understand how to get a collection together, or an individual model. And I can usually get a model's data to display. But I'm not clear at all how to take a collection and get the list of models within that collection to display.
Am I supposed to iterate over the collection and render each model individually?
Is that supposed to be part of the collection's render function?
Or does the collection just have it's own view and somehow I populate the entire collection data into a view?
Just speaking generally, what is the normal method to display a list of models?
From my experience, it's the best to keep in your collection view references to each model's view.
This snippet from the project I'm currently working on should help you get the idea better:
var MyElementsViewClass = Backbone.View.extend({
tagName: 'table',
events: {
// only whole collection events (like table sorting)
// each child view has it's own events
},
initialize: function() {
this._MyElementViews = {}; // view chache for further reuse
_(this).bindAll('add');
this.collection.bind('add', this.add);
},
render: function() {
// some collection rendering related stuff
// like appending <table> or <ul> elements
return this;
},
add: function(m) {
var MyElementView = new MyElementViewClass({
model: m
});
// cache the view
this._MyElementViews[m.get('id')] = MyElementView;
// single model rendering
// like appending <tr> or <li> elements
MyElementView.render();
}
});
Taking this approach allows you to update views more efficiently (re-rendering one row in the table instead of the whole table).
I think there are two ways to do it.
Use a view per item, and manipulate the DOM yourself. This is what the Todos example does. It's a nice way to do things because the event handling for a single model item is clearer. You also can do one template per item. On the downside, you don't have a single template for the collection view as a whole.
Use a view for the whole collection. The main advantage here is that you can do more manipulation in your templates. The downside is that you don't have a template per item, so if you've got a heterogeneous collection, you need to switch in the collection view template code -- bletcherous.
For the second strategy, I've done code that works something like this:
var Goose = Backbone.Model.extend({ });
var Gaggle = Backbone.Collection.extend({
model: Goose;
};
var GaggleView = Backbone.View.extend({
el: $('#gaggle'),
template: _.template($('#gaggle-template').html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
}
};
var g = new Gaggle({id: 69);
g.fetch({success: function(g, response) {
gv = new GaggleView({model: g});
gv.render();
}});
The template code would look something like this:
<script type="text/template" id="gaggle-template">
<ul id="gaggle-list">
<% _.each(gaggle, function(goose) { %>
<li><%- goose.name %></li>
<% }); %>
</ul>
</script>
Note that I use the _ functions (useful!) in the template. I also use the "obj" element, which is captured in the template function. It's probably cheating a bit; passing in {gaggle: [...]} might be nicer, and less dependent on the implementation.
I think when it comes down to it the answer is "There are two ways to do it, and neither one is that great."
The idea of backbone is that view rendering is event driven.
Views attach to Model data change events so that when any data in the model changes the view updates itself for you.
What you're meant to do with collections is manipulate a collection of models at the same time.
I would recommend reading the annotated example.
I've also found this a confusing part of the Backbone framework.
The example Todos code is an example here. It uses 4 classes:
Todo (extends Backbone.Model). This represents a single item to be todone.
TodoList (extends Backbone.Collection). Its "model" property is the Todo class.
TodoView (extends Backbone.View). Its tagName is "li". It uses a template to render a single Todo.
AppView (extends Backbone.View). Its element is the "#todoapp" . Instead of having a "model" property, it uses a global TodoList named "Todos" (it's not clear why...). It binds to the important change events on Todos, and when there's a change, it either adds a single TodoView, or loops through all the Todo instances, adding one TodoView at a time. It doesn't have a single template for rendering; it lets each TodoView render itself, and it has a separate template for rendering the stats area.
It's not really the world's best example for first review. In particular, it doesn't use the Router class to route URLs, nor does it map the model classes to REST resources.
But it seems like the "best practice" might be to keep a view for each member of the collection, and manipulate the DOM elements created by those views directly.