Binding render function with parameters. Backbone.js - javascript

Normally one binds model change event to render function like this:
initialize: function() {
this.model.bind('change', this.render, this);
}
How do I bind model change event to render function with parameter:
render: function(templ) {
this.$el.html(templ);
}
initialize: function() {
// ?
}

Something like this?
this.model.bind('change', function() {
return this.render(templ);
}, this);

You can use underscore bind for partial application (that's the term for what you want to do).
this.model.bind('change', _.bind(this.render, this, 'foo', 'bar'));
So render recieves 'foo' and 'bar' as aguments

Related

Backbone: Fetch before rendering

I'm trying to add a new item to my backbone collection using:
window.bearList.create({ name: "Tina" } );
This correctly saves the new item to the server, because afterwards I can see this on the server, which is what I want. (I'm using MongoDB)
{"name":"Tina","_id":"53b41d92b7083d0b00000009","__v":0}
I have this binding in my bearList ListView:
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('add', this.render);
},
The problem is that the code above just adds the following to my collection view until I reload the page.
{"name":"Tina"}
I've tried using the model.save() callback, but I still have the same issue.
Like I said, everything looks fine on the server, and the collection has the correction version of 'Tina' once I reload the page.
But for some reason, it is not getting the full model for the ListView's 'render' event. I've tried fetching each model individually on the ListView render method, but this did not work and is bad practice anyway.
Can someone help me out?
Here is my full code for this:
window.ListView = Backbone.View.extend({
tagName: 'ul',
className: 'list-group',
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('add', this.render);
},
render: function(){
this.$el.html(" ");
this.collection.each(function(item){
var listItemView = new ListItemView({ model: item });
this.$el.append(listItemView.render().el);
}, this);
return this;
},
});
window.ListItemView = Backbone.View.extend({
tagName: 'li',
className: 'list-group-item',
initialize:function () {
this.model.bind("change", this.render);
},
render:function () {
console.log(JSON.stringify(this.model.toJSON()));
this.$el.html("<a href='#"+ this.model.hashType + "/"+this.model.get('_id')+"' >" + this.model.get('name') + "</a>");
return this;
}
});
Just pass wait:true.
window.bearList.create({ name: "Tina" }, {wait: true} );
http://backbonejs.org/#Collection-create
Pass {wait: true} if you'd like to wait for the server before adding
the new model to the collection.
Listen to the sync event since add will only add the values you are creating from within backbone. http://backbonejs.org/#Sync
And a tip: use listenTo to use more of Backbone's features.
instead of
initialize:function () {
this.model.bind("change", this.render);
},
use:
initialize:function () {
this.listenTo( this.model, "change", this.render );
},

bindAll() seems not to work in backbone View

I have View listening for an "add" event on a Collection. When the handler fires, the context is the Collection, even though I used _.bindAll() to bind it to the View. Is this a bug, or am I not understanding how this works? jsfiddle
V = Backbone.View.extend({
initialize: function (options) {
this.collection.on('add', this.onAdd);
_.bindAll(this, 'onAdd');
},
onAdd: function () { console.log(this); }
});
c = new Backbone.Collection();
v = new V({collection:c});
c.add(new Backbone.Model());
Outputs:
e.Collection {length: 1, models: Array[1], _byId: Object, _events: Object, on: function…}
Put the bindAll method before the binding to collection statement
This should work:
V = Backbone.View.extend({
initialize: function (options) {
_.bindAll(this, 'onAdd');
this.collection.on('add', this.onAdd);
},
onAdd: function () { console.log(this); }
});
UPDATE:
It's possible not to use the _.bindAll method by applying the context in the .on() method
this.collection.on('add', this.onAdd, this);
Your problem is when your call this.collection.on('add', this.onAdd); first an event is bound to the onAdd function with no context (this = collection at trigger time) and calling _.bindAll(this, 'onAdd'); doesn't override it.
Try to change the order :
_.bindAll(this, 'onAdd');
this.collection.on('add', this.onAdd);

Getting the attribute from a View's Model when the view is clicked (backbone.js)

When a user clicks on a div with class .photo_container which is part of the view PhotoListView, there is a function sendSelectedPhotoId that will be triggered. This function has to get the attribute photo_id from the Photo model that belongs to this view whose div .photo_container element has been clicked, and send it to the serverside via fetch().
Problem: So far I managed to get the function sendSelectedPhotoId to be triggered when the div is clicked, but I cant figure out how to get the photo_id attribute of the view's Photo model. How should I achieve this?
On a side note, I'm not sure whether the correct photo_id will be send.
Code
$('#button').click( function() {
// Retrieve photos
this.photoList = new PhotoCollection();
var self = this;
this.photoList.fetch({
success: function() {
self.photoListView = new PhotoListView({ model: self.photoList });
$('#photo_list').html(self.photoListView.render().el);
}
});
});
Model & Collection
// Models
Photo = Backbone.Model.extend({
defaults: {
photo_id: ''
}
});
// Collections
PhotoCollection = Backbone.Collection.extend({
model: Photo,
url: 'splash/process_profiling_img'
});
Views
// Views
PhotoListView = Backbone.View.extend({
tagName: 'div',
events: {
'click .photo_container': 'sendSelectedPhotoId'
},
initialize: function() {
this.model.bind('reset', this.render, this);
this.model.bind('add', function(photo) {
$(this.el).append(new PhotoListItemView({ model: photo }).render().el);
}, this);
},
render: function() {
_.each(this.model.models, function(photo) {
$(this.el).append(new PhotoListItemView({ model: photo }).render().el);
}, this);
return this;
},
sendSelectedPhotoId: function() {
var self = this;
console.log(self.model.get('photo_id'));
self.model.fetch({
data: { chosen_photo: self.model.get('photo_id')},
processData: true,
success: function() {
}});
}
});
PhotoListItemView = Backbone.View.extend({
tagName: 'div',
className: 'photo_box',
template: _.template($('#tpl-PhotoListItemView').html()),
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.close, this);
},
render: function() {
$(this.el).html(this.template( this.model.toJSON() ));
return this;
},
close: function() {
$(this.el).unbind();
$(this.el).remove();
}
});
SECOND ATTEMPT
I also tried placing the event handler and sendSelectedPhotoId in the PhotoListItemView where I managed to get the Model's attribute properly, but I can't figure out how to trigger the reset event when the PhotoList collection did a fetch().
View
PhotoListItemView = Backbone.View.extend({
tagName: 'div',
className: 'photo_box',
events: {
'click .photo_container': 'sendSelectedPhotoId'
},
template: _.template($('#tpl-PhotoListItemView').html()),
initialize: function() {
this.model.bind('change', this.render, this);
this.model.bind('destroy', this.close, this);
},
render: function() {
$(this.el).html(this.template( this.model.toJSON() ));
return this;
},
close: function() {
$(this.el).unbind();
$(this.el).remove();
},
sendSelectedPhotoId: function() {
console.log('clicked!');
var self = this;
console.log(self.model.get('photo_id'));
self.model.fetch({
data: { chosen_photo: self.model.get('photo_id')},
processData: true,
success: function() {
$(this.el).html('');
}});
}
});
Problem: With this, I cant seem to fire the reset event of the model after doing the fetch() in function sendSelectedPhotoId, which means I cant get it to re-render using PhotoListView's render().
In the screenshot below from Chrome's javascript console, I printed out the collection after sendSelectedPhotoId did its fetch(), and it seems like the fetched added the new data to the existing model, instead of creating 2 new models and removing all existing model!
You already have child views for each model, so I would put the click event handler in the child view. In the handler in the child, trigger an event passing this.model, and listen for that event in your parent.
Update based on update:
Try changing
this.model.bind('reset', this.render, this); to
this.model.bind('remove', this.render, this); // model is a collection right?
and then remove the model from the collection after the view is clicked. Also, I don't think using Model.fetch is what you really want to do. Maybe a .save or a custom method on the model?
Update based on author's comment showing sample base from blog
I would not follow that blog's advice. If you are using backbone professionally I can't recommend the Thoughtbot ebook enough.
It's $50 for a work in progress, and it's worth every penny
It has a simple sample application that lays out how to organize a backbone app. This is why I bought the book.
It uses Rails in the examples for the backend, but I have used Rails, Node, and C# MVC and all work no problem.

SerializeData in CompositeView

I need to pass a value to the listView.template in order to be aware the template about the collection.length.
I think one option is to redefine the serializeData method in order to pass a parameter in this way.
var ListView = Marionette.CompositeView.extend({
initialize: function () {
this.collection.on('reset', this.serializeData, this);
this.collection.on('remove', this.serializeData, this);
this.collection.on('add', this.serializeData, this);
},
serializeData: function () {
console.log(this.collection.length);
return {
has_tasks: this.collection.length > 0
};
},
// other codes
});
When I start the app the collection is not yet fetched so:
1.a) the collection.legth = 0
2.b) the template get has_tasks = false as expected.
2.a) after the fetch the collection.length is > 0,
2.b) the serializeData is called and so it puts the has_tasks = true,
2.c) the template seems to be not rendered because it maintains the has_tasks = false
Any idea because 2.c?
Latest Marionette has solved this problem by calling an optional templateHelpers on the view to provide additional context to the view. Also your event binding is not Marionette-friendly as it it will not be auto-unbound correctly when the view is unloaded. So all you need to do in your view is:
var ListView = Marionette.CompositeView.extend({
initialize: function () {
this.bindTo(this.collection, "add", this.render, this);
this.bindTo(this.collection, "remove", this.render, this);
this.bindTo(this.collection, "reset", this.render, this);
},
templateHelpers: function () {
console.log(this.collection.length);
return {
has_tasks: this.collection.length > 0
};
},
// other codes
});
Note, however, that you probably don't want to rerender the entire view and all the sub-elements every time an item is added or removed. A better approach is to only update the count displayed. For instance:
var ListView = Marionette.CompositeView.extend({
initialize: function () {
this.bindTo(this.collection, "add", this.updateCount, this);
this.bindTo(this.collection, "remove", this.updateCount, this);
this.bindTo(this.collection, "reset", this.updateCount, this);
},
updateCount: function() {
this.$('.count_span').html(this.collection.length);
},
// other codes
});
I would simply use something like:
var ListView = Marionette.CompositeView.extend({
initialize: function () {
this.bindTo(this.collection, 'reset', this.render)
},
serializeData: function () {
return { has_tasks: this.collection.length > 0 }
}
});
Calling serializeData again will have no effect on your view. You need to render it again in order to show the new values (because render will get the data by calling serializeData again).
Anyway what is the point of sending hasTask to the template since you can access the collection (and thus its length)?

no existant $(this.el) in backbone view while rendering child view

full code here... http://pastebin.com/EEnm8vi3
line 378 is not inserting the sections into the current view. the section model is correctly being passed into the method. everything else works as expected except for the insertion of the child rendered views.
I am wanting to know why $(this.el) is empty and therefore not allowing an append. trying to use a direct selector like $('#sections') also does not work.
relevent code repeated from pastbin link above: (addOne method)
SCC.Views.SectionView = Backbone.View.extend({
tagName: "div",
className: "section",
initialize: function(){
_.bindAll(this, 'render');
this.template = _.template($('#section-tmpl').html());
},
render: function() {
console.log($(this.el));
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
SCC.Views.SectionsView = Backbone.View.extend({
tagName: "div",
id: "sections",
className: "sections",
initialize: function(){
_.bindAll(this, 'render');
//SCC.Sections.bind('add', this.addOne, this);
SCC.Sections.bind('reset', this.addAll, this);
SCC.Sections.bind('all', this.render, this);
},
render: function() {
$(this.el).html("<p>rendered</p>");
return this;
},
addOne: function(section) {
var view = new SCC.Views.SectionView({model: section});
$(this.el).append(view.render().el);
},
addAll: function() {
this.collection.each(this.addOne);
}
});
SCC.Sections = new SCC.Collections.Sections();
SCC.SectionsView = new SCC.Views.SectionsView({collection:SCC.Sections});
SCC.Sections.reset(window.SectionData);
$('#main').append(SCC.SectionsView.render().el);
I ran into this problem myself and so I'll leave this answer for anyone else out there:
When you bind this.render to 'all' as #lukemh did:
SCC.Sections.bind('all', this.render, this);
You're effectively saying everytime an event is triggered in the model/collection re-render the view. When you use .html() in that render method you're also going to override any child views that may have been .append()'ed to it throught the addOne function.
If you move the $(this.el).html() call to the initialize view the problem is solved. You can still bind render to 'all' but make sure you're only re-rendering a portion of it or else you'll override the child views again.

Categories