I am trying to place the rendered output of a Collection View onto the dom. However, only the last object in the collection is displayed on the page at the end of the process.
I have a event handler set up on the view so that when an item is clicked, it's title is logged out. Whenever I click on this single element that is placed onto the Dom, the title for each of my objects is logged, even though only one is displayed, so each handler is being applied to the final element but is somehow logging out the correct titles.
Does anybody know how I can render out each item in the collection rather than just the final one? Below is a quick tour through my code.
The end goal is to list out the name of each film.
Model
First, define the model - nothing exciting here
var FilmModel = Backbone.Model.extend({});
View
Here is a simplified version of the View I have made for the Film model
var FilmView = Backbone.View.extend({
// tagName: 'li',
initialize: function() {
this.$el = $('#filmContainer');
},
events: {
'click': 'alertName'
},
alertName: function(){
console.log("User click on "+this.model.get('title'));
},
template: _.template( $('#filmTemplate').html() ),
render: function(){
this.$el.html( this.template( this.model.attributes ) );
return this;
}
});
Collection
Again, seems standard.
var FilmList = Backbone.Collection.extend({
model: FilmModel,
});
Collection View
Adapted from a Codeschool course I took on Backbone
var FilmListView = Backbone.View.extend({
// tagName: 'ul',
render: function(){
this.addAll();
return this;
},
addAll: function(){
this.$el.empty();
this.collection.forEach(this.addOne, this);
},
addOne: function(film){
var filmView = new FilmView( { model: film } );
this.$el.append(filmView.render().el);
// console.log(this.$el);
}
});
Go time
var filmList = new FilmList({});
var filmListView = new FilmListView({
collection: filmList
});
var testFilms = [
{title: "Neverending Story"},
{title: "Toy Story 2"}
];
filmList.reset(testFilms);
filmListView.render();
From my understanding of Backbone so far, what this should be doing is appending, using the template specified in FilmView to render each item in the filmList collection into the el in the filmListView.
However, what actually happens is that the final title is always placed on the DOM.
I initially (when this was pulling in from an API) thought that the issue might be similar to this question, however now that I am resetting with my own testFilms, I can be positive that I am not overriding or setting any id attribute that I shouldn't.
Does anybody have any ideas?
I think it could be that you set the el of FilmView to an id, which should always be unique, however then you loop over the collection and continually reset that el/id with the current model since each FilmView is going to have the same el
Related
Being new to Backbone.js, I try to develop a SPA following, amongst others, Addy Osmani's "Developing Backbone.js Applications". It's exercise 2 (http://addyosmani.github.io/backbone-fundamentals/#exercise-2-book-library---your-first-restful-backbone.js-app) shows, how to use a collection view to render inner model views from each of the collections objects. However, the collection view in this example does not come with its own html markup. Thus, the collection's models are associated with the collection view's DOM element (here: '#books'). I want to use an own template to first render the html elements of my collection view, say, a simple div with id="the-plan". Problem is, "#the.plan" is not recognized from the inner model views as element attribute. Hence, the inner views are not rendered at all. There is no error message and all console.logs are working. Code looks something like this:
app.PlanItemView = Backbone.View.extend({
className: "plan-item",
template: _.template($("#plan-item-view-template").html()),
render: function(){
console.log("Rendering plan item view...");
this.$el.append(this.template(this.model.toJSON()));
return this;
}
});
app.PlanView = Backbone.View.extend({
el: ".main-panel",
id: "#the-plan",
template: _.template($("#plan-view-template").html()),
initialize: function(initialPlanItems){
console.log("Plan View initialized... Selector: " + this.id);
console.log("Incoming initial plan item data: " + _.first(_.values(_.first(initialPlanItems))));
this.collection = new app.MealPlan(initialPlanItems);
this.render();
},
// render plan by rendering each item in its collection
render: function() {
this.$el.append(this.template({
"myPlan": this.collection.each(function(item){
this.renderPlanItem(item);
}, this)
}));
return this;
},
// render a plan item by creating a PlanItemView and appending the
// element it renders to the plan's id-element ('#the-plan')
renderDish: function(item){
var planItemView = new app.PlanItemView({
model: item,
el: this.id
});
this.$("#the-plan").append(planItemView.render());
}
});
...
var planView = new app.PlanView(test_plan_items);
What's wrong here?
Change the render function to :
render: function() {
this.$el.append(this.template({
"myPlan": this.collection
}));
this.collection.each(function(item){
this.renderPlanItem(item);
}, this);
return this;
}
And change the renderDish to :
renderPlanItem: function(item){
var planItemView = new app.PlanItemView({
model: item,
el: this.id
});
planItemView.render();
}
I'm creating a Backbone app with a section to view reports; the section has three parts: a menu of report links, the title of the displayed report, and the content of the displayed report. The user is to click on a report link, which will fetch the associated model's data. Then the report title and content should update accordingly. However, I'm not sure how the view bindings should work, and each report may return slightly different data that requires a different view template. Here's my JSFiddle (fetch method overridden just for this example)
Right now, I have a Backbone model for each report and a Backbone collection of all the reports:
App.Models.Report = Backbone.Model.extend();
App.Collections.Reports = Backbone.Collection.extend({
model: App.Models.Report,
url: "/reports"
});
The menu view is tied to the collection and on click, sets App.State.title and App.State.cid, which the other two views are listening to:
App.Views.ReportLink = Backbone.View.extend({
tagName: 'li',
className: 'is-clickable',
initialize: function() {
this.render();
},
render: function() {
this.el.innerHTML = this.model.get('title');
this.$el.attr('data-CID', this.model.cid); // store the model's cid
}
});
App.Views.ReportMenu = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.listenTo(this.collection, 'reset', this.render)
this.render();
this.$el.on('click', 'li', function() {
App.State.set({
'title': this.innerHTML,
'cid': $(this).attr('data-CID') // cid of the clicked view's model
});
});
},
The difficulty is with the report content; what it currently does is listen for changes to App.State.cid and then calls fetch on the given model with that cid. This fetch populates the model with a sub-collection of report rows. The report content view then set its html based on the sub-collection data, and it is also supposed to apply the correct template to the data:
App.Views.ReportContent = Backbone.View.extend({
initialize: function(attrs) {
this.listenTo(this.model, 'change:cid', this.render);
this.reportsCollection = attrs.reportsCollection;
},
render: function() {
var self = this,
cid = this.model.get('cid'),
model = this.reportsCollection.get(cid);
model.fetch({
success: function() {
var html = '';
model.subCollection.each(function(model) {
var template = _.template($('#templateReportA').html()); // want to dynamically set this
html += template(model.toJSON());
});
self.$el.html(html);
}
});
}
});
1) Is this the correct sort of implementation for this type of multiple view situation with a collection?
2) How can I pass the correct template that needs to apply for each individual report? Right now I'm explicitly passing the view template for report A. I can think of storing it on the model but the template should be associated with the view.
If your cids are all made up of characters that are legal in HTML ids, then a simple solution would be to name all your report templates templateReportxxx where "xxx" is the report's cid and then just change the template-loading line to
var template = _.template($('#templateReport'+cid).html());
I'm trying to develop my first backbone application. All seems ok, but when i render the view and append some html to the $el, nothing is rendered in the page.
Rest service calls done ok, the Backbone.Router.extend is declared inside $(document).ready(function () {}); to ensure that the DOM is created.
Debugging my javascript, the el element get to contain the correct value in the innerHTML property, but when the whole page is rendered, this value doesn't appear in the page.
¿What am i doing wrong?
My View code:
window.ProductsListView = Backbone.View.extend({
id: 'tblProducts',
tagName: 'div',
initialize: function (options) {
this.model.on('reset', this.render, this);
},
render: function () {
// save a reference to the view object
var self = this;
// instantiate and render children
this.model.each(function (item) {
var itemView = new ProductListItemView({ model: item });
var elValue = itemView.render().el;
self.$el.append(elValue); // Here: the $el innerHTML is ok, but in the page it disappear. The id of element is div#tblProducts, so the element seems correct
});
return this;
}
});
window.ProductListItemView = Backbone.View.extend({
tagName: 'div',
template: _.template(
'<%= title %>'
),
initialize: function (options) {
this.model.on('change', this.render, this);
this.model.on('reset', this.render, this);
this.model.on('destroy', this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
// $(this.el).html('aaaaaa'); // This neither works: it's not a template problem
return this;
},
close: function () {
$(this.el).unbind();
$(this.el).remove();
}
});
Here i load products (inside Backbone.Router.extend). This is executed correctly:
this.productsList = new ProductsCollection();
this.productsListView = new ProductsListView({ model: this.productsList });
this.productsList.fetch();
And this is the html element i want to render:
<div id="tblProducts">
</div>
Thanks in advance,
From the code you have posted, you are not actually inserting your ProductsListView in to the DOM or attaching it to an existing DOM element.
The way I like to look at it is you have two types of Views:
Those that are dynamically generated based on data returned from the server
Those that already exist on the page
Usually in the case of lists, the list already exists on the page and it's items are dynamically added. I have taken your code and restructured it slightly in this jsfiddle. You will see that the ProductListView is binding to an existing ul, and ProductItemView's are dynamically appended when they are added to the Collection.
Updated jsfiddle to demonstrate Collection.reset
The el property exists within the view if it is rendered or not. You can't say it is ok there because Backbone will create an element if no element is passed (empty div).
If you want to render the view you should determine what is the container of the element? Do you have an html you want to attach the view to?
Try passing a container element by calling the view with an el like
this.productsListView = new ProductsListView({ model: this.productsList, el : $("#container") });
Of course you can create the view and attach it to the DOM later:
el: $("#someElementID") //grab an existing element
el.append(view.render().el);
Your view wont exist in the dom until you attach it somewhere.
Yip I am a novice to backbone and underscore. First of all let me say I have read through all the online examples, but I'm still missing something.
Whats happening is I'm loading up my list of objects fine, but when I click delete its going through all the objects. I know this is because I'm not assigning the individual items correctly, but I cannot see what is causing this.
I would love some help.
Here is basic html code
<div id="itemid" class="view">
<span class="text">{{-text}}</span>
<a id="dele" data-role="button" data-inline="true" data-icon='delete' >Delete</a>
</div>
This is my itemlist code
bb.view.List = Backbone.View.extend(_.extend({
tagName: "ul",
initialize: function( items ) {
var self = this
_.bindAll(self)
self.setElement('#itemid')
self.elem = {
text: self.$el.find('#text')
}
self.items = items
},
render: function(items) {
var self = this
self.$el.empty()
self.items.each(function(item){
var itemview = new bb.view.Item({
model: item
})
itemview.render()
})
}
},scrollContent))
Now finally the itemview for individual items, note the template code below.
bb.view.Item = Backbone.View.extend(_.extend({
tagName: "li",
events: {
'tap #dele': function(){
var self = this
self.removed()
return false;
}
},
render: function(){
var self = this
_.bindAll(this)
self.setElement('#itemid')
self.elem = {
dele: self.$el.find('#dele')
}
var html = self.tm.item( self.model.toJSON() )
$(this.el).append( html )
},
removed: function()
{
var self = this
this.model.removed();
}
},{
tm: {
item: _.template( $('#itemid').html() )
}
}))
Hope someone can help
mark
I know it's not directly related to your question, but I have couple suggestions re. your code:
You seem to use var self = this; self.method() everywhere. You do not to do this unless you need to pass this into a closure. But yes, you need it when you go into each iterator.
You do not need to call _.bindAll(self) on all methods. Again, you are better off explicitly binding this to methods, e.g.:
this.collection.bind('reset', this.render, this);
this.el.on('click', 'a', this.handleClick.bind(this));
You probably don't realize and possibly are not even concerned about your client's resources at this point, but binding your environment to this creates a lot of closures (allocating memory) that you will never use.
Now with your issue, I would refactor your code as follows:
var itemTmp = $('#itemid').html();
bb.view.Item = Backbone.View.extend({
tagName: "li",
events: {
'tap #dele': "deleteItem"
},
render: function(){
this.el = _.template(itemTmp)(this.model.toJSON());
},
deleteItem: function() {
this.model.destroy(); // deletes model
this.remove(); // deletes view
}
});
bb.view.List = Backbone.View.extend({
tagName: "ul",
initialize: function(items) {
this.setElement('#itemid');
this.collection = items;
this.render();
},
render: function() {
var self = this; // yes, you will need it in the iterator
this.$el.empty();
this.collection.each(function(model){
var itemview = new bb.view.Item({
model: model
});
itemview.render();
self.$el.append(itemview.el);
});
}
});
_.extend(bb.view.List.prototype, scrollContent);
Note that Item view does not insert its HTML into DOM (or parent view element). Basically it's a better practice when sub-views or dependencies do not access parent's element. Therefore you can either have render() return HTML, or you can access View's instance el attribute from the module that instantiated it.
Last observation -- when you have an app with A LOT of views, you don't want to create new View instance for each list item. You're better off inserting DOM nodes with unique id's and then on DOM events read these ids and parse item ids, and look up items by this.collection.get(id). But again, this is coming from pure performance considerations.
Here is my content.js in which i am using backbone.js for rendering contents.
// Our basic **Content** model has `content`, `order`, and `done` attributes.
var Content = Backbone.Model.extend({
// If you don't provide a Content, one will be provided for you.
EMPTY: "empty Content...",
// Ensure that each Content created has `content`.
initialize: function() {
}
});
var ContentCollection = Backbone.Collection.extend({
model : Content
});
// Create our global collection of **Todos**.
window.Contents = new ContentCollection;
// The DOM element for a Content item...
var ContentView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
events: {
"click .content": "open"
},
// a one-to-one correspondence between a **Content** and a **ContentView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
this.model.view = this;
},
// Re-render the contents of the Content item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
Here is how i am making the list of content and rendering them.
for(var i=0; i<data.length; i++) {
var content = new Content(data[i]);
var templ=_.template($('#tmpl_content').html());
var view = new ContentView({model: content});
view.template=templ;
$("#content").append(view.render().el);
}
my question is how can i get the contetnt model listing .
as i have created the collection
var ContentCollection = Backbone.Collection.extend({
model : Content
});
// Create our global collection of **Todos**.
window.Contents = new ContentCollection;
So when i do watch Contents it shows length 0 and models [] .
how contetnt will get added in the collection . or how to see list of model in backbone.js
You need to Collection.add(models) before it will contain anything.
You also could specify a URL (which should return a JSON array of models) on your collection and then do window.Contents.fetch(). Backbone will auto-populate the model (Content) specified in your collection and automatically add them to your collection.