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.
Related
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
I started to learn Marionette.View concept.for this I created model with 1 attribute and assign default value.
I have dropdown list in my html file. Whenever it changes it triggers a function name as myfunction.Inside myFunction I changed model attribute value.
Whenever attribute value changes automatically it triggers another function.that event I written inside Marionette.CompositeView.but it's not working.
Earlier I did same thing using myModel.on there it's working fine.But it's not working modelEvents in Marionette.CompositeView.
let's check the following code.
var mymodel=Backbone.Model.extend({
defaults:{
"name":"oldValue"
}
});
var mymodelObj=new mymodel();
//marionette.View code
var backView=Backbone.Marionette.CompositeView.extend({
events:{
"change #pop1":"myFunction"
},
myFunction:function(){
mymodelObj.set({name:"updatedValue"})
},
modelEvents:{
"change:name":"myFunction1"
},
myFunction1:function(){
alert("Hi");
}
});
//creating instance
var backViewObj=new backView({el:$("#sidebar")});
How can I fix this.
Right Now I am trying to understanding where Marionette.js useful in my Backbone Applications.
If I am not mistaken, model is not attached to the view you have created. For modelEvents to be triggered, there should be a model attribute in CompositeView. For this you should specify the model during initialization of CompositeView;
var backViewObj=new backView({el:$("#sidebar"), model : mymodelObj});
To do this though you need to pass the model in when creating the backView like so:
var backViewObj = new backView({ el:$("#sidebar"), model: myModel });
Marionette accomplishes this by overriding delegateEvents which Backbone.View calls in it's constructor and delegating your modelEvents object to the model:
Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
Based on your comments above I think that you're unclear about the different types of views that Backbone.Marionette provides as you are asking how to pass more model instances. I also was unclear when I began using Marionette.
Marionette.ItemView represents a single model.
Marionette.CollectionView represents a collection and renders a Marionette.ItemView for every model in the collection. It does not have the ability to render a template.
Marionette.CompositeView represents both a model and a collection (leaf and branch in a tree structure). This is for recursive data structures although you can use a CompositeView to render a template (like a table header, etc.) and use itemViewContainer to place the itemViews in a specific element.
If you want your CompositeView to be able to render multiple models you'll need to pass a collection of models into the CompositeView and not one single model:
In one of the example i picked from SO answers here and many BackBoneJs examples i see that the initialize function knows which view the model is going to be rendered with. I don't know i am kind of biased now, is this a good practice or it depends on type of application being developed.
Example
http://jsfiddle.net/thomas/Yqk5A/
Edited Fiddle
http://jsfiddle.net/Yqk5A/187/
Code Reference
FriendList = Backbone.Collection.extend({
initialize: function(){
this.bind("add", function( model ){
alert("hey");
view.render( model );
})
}
});
Is the above a good practice or below
var friendslist = new FriendList;
var view = new FriendView({el: 'body'});
friendslist.bind("add", function( model ){
alert("hey" + model.get("name"));
view.render( model );
})
in the edited fiddle collection is rendered by a view, and we also can use many more views to render the collection.
I am all for using events,
I myself don't want to move the bind's outside the model, I'd keep them there like the original example
var app = {};
app.evt = _.extend({}, Backbone.Events); // adding a global event aggregator
FriendList = Backbone.Collection.extend({
initialize: function(){
this.bind("add", function( model ){
alert("hey");
app.evt.trigger('addfriend', model);
})
}
});
//further in your code you can bind to that event
app.evt.bind("addfriend", function(model){
var view = new FriendView({el: 'body'});
view.render(model);
});
however, i find the example a bit weird, creating a new view, with body as element, and rendering it with giving a model to the render function. i'd find it more logic if the view is created with a model as attribute, and then rendering the content into the body. but thats another subject all together.
in short, i move creating the view outside, listening to an event being triggered, but the bind on the collection stays in the collection code. i find it more managable to keep all the collection code at the same place.
I don't think the collection should know about the view that is used to render it. I know in my projects, the same collection is render in multiple ways so that approach would deteriorate rapidly.
In general I pass the collection to the view that renders the collection and the view will listen to add/remove/update events of the collection to render the elements. The collection view will have knowledge of the child view.
Check out the following link (3rd blog in a series) and in particular the UpdatingCollectionView. This is the approach that I've found useful.
http://liquidmedia.ca/blog/2011/02/backbone-js-part-3/
I have a collection of models. When a model changes it triggers a change event on the collection. I watch for the collection change event and then I update the UI.
How should I go about updating the UI? Don't I need to know what models are new, so I can append, and what already exist, so I can update?
One of the reason I feel I need this granularity is because there's an animation transition, so I need to relate every model to it's previous state. Does backbone help with this or should I just build this on my own?
to know which models are new, listen to the collection's "add" event. then you can render the individual item.
MyView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "renderItem");
this.collection.bind("add", this.renderItem);
},
renderItem: function(item){
// render the new item here
},
render: function(){
this.collection.each(this.renderItem);
}
});
in this example, rendering the collection works the same as rendering an individual item - it calls the same renderItem method that the "add" event calls.
to handle the scenario where you have a sorted list... you'll have to do some logic in your renderItem method to figure out the location of the item. something like this maybe (untested code... just an idea):
MyView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "renderItem");
this.collection.bind("add", this.renderItem);
},
renderItem: function(item){
var itemView = new ItemView({model: item});
itemView.render();
return itemView;
},
render: function(){
this.collection.each(function(item){
var itemView = renderItem(item);
var itemIndex = item.get("index");
var previousItem = this.$(".myList")[itemIndex];
$(itemView.el).insertAfter($(previousItem));
});
}
});
this code assumes you have an index attribute on your models to know the order that the models are supposed to be in.
also note that there's a high likelihood that this code won't execute as-is, since i haven't tested it out. but it should give you an idea of the code you'll want to write.
You don't need to go through the collection if the model changes. Simple render a sub view for each model and in which you can handle the change event.
Imagine you have the following structure:
Collection: Tasks
Model: Task
And two views:
Tasks view (main view)
Task view (sub view)
In the #render method of the Tasks view you render e.g. a <ul> element which will hold your tasks (sub views). Then you iterate through all your collection's models and create a Task view for each, passing the model and appending it's el to your main views <ul>.
Within your Task view you bind the #render method to the models change event and each Task view will take care of itself. — Am I making sense?
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.