How to bind existing dom elements to backbone collection view - javascript

I have a series of comments on a page, which can be edited. My idea was to render the comments by Rails and preload a json with all those comments in a Backbone Collection.
Then I would poll every x seconds, to see if there are changes. Normally I render the collection by looping over all the models, and create a view for each item. When a model gets updated, so will the view (comment im this case).
But my question is this, how do you bind a view to the model, when the view is already in the DOM. Especially since the view had a dynamic id. There is no point in rendering the view, since it's already there. When you render a view, backbone binds it through somekind of cid.
The only solution I can think of, is by setting an id in the dom object on pageload. iow
<div id="comment-<%= content.id %>"></div>
. And then in the initialize of the view, reset the id
class Comment extends Backbone.View
initialize: ->
#id = "comment-" + #model.get('id')
But I'm not sure if thats the way to go. Would events still be binded?

Special for you :)
var CommentsView = Backbone.View.extend({
tagName : 'ul',
comments : {},
initialize : function () {
this.listenTo(this.collection, 'add', this.addComment);
this.listenTo(this.collection, 'remove', this.removeComment);
this.listenTo(this.collection, 'change', this.updateComment);
},
addComment : function (model) {
this.comments[model.id] = new CommentView({model:model});
this.$el.append(this.comments[model.id].render().$el);
},
removeComment : function (model) {
this.comments[model.id].remove();
this.comments[model.id] = null;
},
updateComment : function (model) {
this.comments[model.id] = new CommentView({model:model});
this.$('[data-id="' + model.id + '"]').before(this.comments[model.id].render().$el).remove();
}
});
var CommentView = Backbone.View.extend({
tagName : 'li',
template : _.template('<div data-id="<%= id %>"><%= name %>: <%- message %></div>'),
render : function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
// comments
var initialComments = [{id:1, name:'user1', message:'great!'}, {id:2, name:'user2', message:':)'}];
var actualComments = [{id:1, name:'user1', message:'great! [edited]'}];
var comments = new Backbone.Collection();
var commentsView = new CommentsView({collection:comments});
// show comments
commentsView.render().$el.appendTo('body');
// simulate fetch
comments.add(initialComments);
// simulate update
_.delay(function() {
comments.update(actualComments);
},
2000);
jsfiddle

Related

Storing view class names on Backbone models

I'm struggling with a design decision and looking for some feedback. I don't think this question is necessarily specific to Backbone, but that's the framework I'm currently using.
I'm wondering if it's considered bad practice to store the classname of a view as part of a model. For example, let's say you have a parent view with multiple subviews of different types. Each subview contains an edit link, and when that edit link is clicked, the parent view should update it's contents to contain the edit view for that model. I'm using an "event bus" to orchestrate events.
For example:
var E = _.extend({}, Backbone.Events);
var ParentView = Backbone.View.extend({
initialize: function(options) {
this.apples = options.apples; // Backbone Collection of Apple models
this.oranges = options.oranges; // Backbone Collection of Orange models
this.$appleList = this.$('#apples');
this.$orangeList = this.$('#oranges');
this.$editScreen = this.$('#edit-screen');
// listen to edit events for models and render the
// edit screen for using the appropriate view
this.listenTo(E, 'edit', this.showEditScreen);
},
template: 'templates/parent',
render: function() {
this.$el.html(this.model.toJSON());
this.renderAppleViews();
this.renderOrangeViews();
},
renderAppleViews: function() {
var view = new AppleListView({collection: this.apples});
this.$appleList.html(view.render().el);
},
renderOrangeViews: function() {
var view = new OrangeListView({collection: this.oranges});
this.$orangeList.html(view.render().el);
},
// Show the edit screen for a particular model
showEditScreen: function(model) {
var view = new window[model.editScreenViewClass]({model: model}):
this.$editScreen.html(view.render().el);
}
});
AppleListView and OrangeListView simply loop through their respective collections and append a view to the list.
AppleListView adds AppleItemViews, and OrangeListView adds OrangeItemViews. I'm showing the relevant parts of those views below:
var AppleItemView = Backbone.View.extend({
events: {
'click .edit': 'onEditClick'
},
onEditClick: function(e) {
e.preventDefault();
E.trigger('edit', this.model);
}
});
var OrangeItemView = Backbone.View.extend({
events: {
'click .edit': 'onEditClick'
},
onEditClick: function(e) {
e.preventDefault();
E.trigger('edit', this.model);
}
});
Here is what the models would look like for this to work:
var Apple = Backbone.Model.extend({
editScreenViewClass: 'AppleEditView'
});
var Orange = Backbone.Model.extend({
editScreenViewClass: 'OrangeEditView'
});
I'm asking if it seems "ok" to store this editScreenViewClass on the model. That way I can retrieve it directly from the model passed into the event.

Backbone Collection View is only rendering last object in the collection

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

Render model view within collection view does not know its element?

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

Managing multiple views for a Backbone collection

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

how to see list of models in backbone.js

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.

Categories