I am trying to build a Backbone application with require.js, AMD and handlebars for template.
Here is the code for my index view.
define([
'jquery',
'underscore',
'backbone',
'handlebars',
'collection/questions',
'helpers'
], function($, _, Backbone, Handlebars, QuestionsCollection, Helpers){
// Main Index View
var IndexView = Backbone.View.extend({
el: "#content",
template: Helpers.template('index'),
initialize: function(){
this.questions = new QuestionsCollection();
this.questions.on('sync', this.render, this);
this.questions.fetch();
},
render: function(){
this.$el.html(this.template(this));
this.questions.each(this.addQuestion, this);
return this;
},
addQuestion: function(question){
var view = new IndexView.Question({ model: question });
view.render();
},
count: function(){
console.log(this);
return this.questions.length;
}
});
// Individual Question View
IndexView.Question = Backbone.View.extend({
render: function(){
// console.log(this.model);
}
});
return IndexView;
});
Here everything is working as expacted. But now I want to have a helper function count that will return the number of models in the collection. That way I can use {{count}} in my handle bar template to print the something like. 'There are 8 questions'. But I am having problem with scope.
Inside count function this refers to the window but not to the collection. How will I be able to get reference to this inside count as question collection. I am planning to use many of those helper functions in my app. So need some solid way to do it.
THanks.
You can use a 'bindAll' function from Underscore.js as follows:
initialize: function () {
_.bindAll(this, 'count');
// your initialize code
}
Basically, it replaces your 'count' method with a code similar to following:
var thisReference = this;
var originalCount = this.count;
this.count = function () {
originalCount.apply(thisReference, Array.prototype.slice.call(arguments));
};
I.e., it just saving the original 'this' reference, and passing it when the 'count' method is called.
Today browsers have built-in support for this idiom (see Function.bind).
Nevertheless, in this case it'd be better to pass the count as a template variable:
render: function () {
this.template({
count: this.count()
});
}
Related
I'm having a hard time initializing a simple view with a collection that I'm pulling from an API. Everything I try returns an empty array when I try to return the collection from within the view.
app.models.Employee = Backbone.Model.extend({
});
app.collections.Employees = Backbone.Collection.extend({
Model: app.models.Employee,
url: "http://api.sample.com",
initialize: function(){
this.fetch();
},
parse: function(response){
console.log(response)
},
});
app.views.Container = Backbone.View.extend({
el: $('.container'),
initialize: function(){
this.collection = new app.collections.Employees();
console.log(this.collection)
var that = this;
this.collection.fetch({
success: function(){
that.render();
}
});
},
render: function(){
console.log(this.collection) //returns no models
}
});
You have to use parse correctly:
parse: function(response) {
console.log(response);
return response;
} //`,` - remove it at last item in object
There are 3 problems with your code
You are using parse incorrectly. Per the specifications
The function is passed the raw response object, and should return the
array of model attributes to be added to the collection
so yes, as #Sergey suggested
parse: function(response) {
console.log(response);
return response;
}
or better, just remove your parse function, don't override it.
You are calling your fetch function twice.
First time at this line
this.collection = new app.collections.Employees();
which eventually calls this function
initialize: function(){
this.fetch();
},
Second time is inside your initialize function in the View.
3) You break render
Again, same thing as your parse. You are supposed to
renders the view template from model data, and updates this.el with
the new HTML
Something like this
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
}
http://jsfiddle.net/sbel/3KAhC/
In this fiddle code the even though the stopListening method is called the on method is still in effect as can be seen by the output. What am I doing wrong?
JavaScript:
// Define View
var SomeView = Backbone.View.extend({
initialize: function () {
// Listen to the model
this.model.on('change', this.render, this);
},
render: function () {
$("#message").append("<br>render called");
},
close: function () {
this.stopListening();
}
});
var SomeModel = Backbone.Model.extend({});
var someModel = new SomeModel({
name: 'Foo'
});
var someView = new SomeView({
model: someModel
});
someModel.set('name', 'Bar');
someView.close();
someView = null;
someModel.set('name', 'Zoo');
Use this.listenTo(this.model, 'change', this.render) instead of this.model.on('change', this.render, this):
listenTo object.listenTo(other, event, callback)
Tell an object to
listen to a particular event on an other object. The advantage of
using this form, instead of other.on(event, callback), is that
listenTo allows the object to keep track of the events, and they can
be removed all at once later on.
And an updated Fiddle http://jsfiddle.net/nikoshr/3KAhC/1/
IM trying to bind a template to a Backbone view like this:
SearchView = Backbone.Model.extend({
initialize: function () {
this.render();
},
render: function(){
var template = _.template( $("#search_template").html(), {});
this.$el.html(template);
}
});
var search_view = new SearchView({ el: $("#search_container") });
But im always getting the error: Cannot call method 'html' of undefined
What am I missing here?
Fiddle: http://jsfiddle.net/lmartins/hhpSV/
Thanks.
Ok, pretty basic mistake.
I was creating a model instead of a view.
The first line should be:
SearchView = Backbone.View.extend({
Getting the following error when initializing a Backbone View:
this.collection.on is not a function
I have the following View:
define([
'jquery',
'underscore',
'backbone',
'events',
'utils',
'text!../../templates/vehicles-template.html'
],
function (
$,
_,
Backbone,
vent,
utils,
vehiclesTemplate) {
var VehiclesView = Backbone.View.extend({
template: vehiclesTemplate,
initialize: function () {
this.collection.on('reset', this.render, this); //error: this.collection.on is not a function
utils.log('Vehicles view initialized');
},
render: function() {
this.$el.empty().html(this.template({ vehicles: this.collection.toJSON() }));
return this;
}
});
return VehiclesView;
});
this is returning the view, but does not fails on collection or model when trying to bind to events.
What I am missing here?
As discussed in question comments, you need to pass the collection property to the view:
var view = new VehiclesView({collection:collection});
A View normally expects an object with these attributes before it can render:
{ el: '#someelement', model: someModel }
A View also allows us to bind the model's events to functions in the view:
initialize: function() {
this.model.bind('change', this.renderFromModel, this);
},
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
renderFromModel: function() {
var t = _.template($('#some-template').html());
$('item-' + this.cid).html(t(this.toJSON()));
return this;
},
The problem is that the first time we instantiate a View for rendering, it is expecting an object with a Model in it; and the second time we render the view when it is called from within the Model, it is not. Because of this, I end up creating two render() functions.
Is there a better way of achieving single item render that can also respond to model.change() events?
i think you need to ensure your render method is always bound the view by calling underscore.js' bindAll method.
SomeView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "render");
this.model.bind('change', this.render);
},
render: function(){
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
A better solution is to use the listenTo function:
SomeView = Backbone.View.extend({
initialize: function(){
this.listenTo(this.model, 'change', this.render);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
This way the view object is aware of the bindings it made, and all of them can be removed with the stopListening function and doesn't need to call bind or bindAll explicitly. Last but not least the code is cleaner in my opinion.
Use _.bind() method to set scope
this.model.bind('change', _.bind(this.render, this));
As of Backbone 0.9.2 (and possibly earlier), the on() or bind() function (as well as its counterpart off() or unbind()) takes an optional context parameter to use for this when called.
So,
SomeView = Backbone.View.extend({
initialize: function(){
_.bindAll(this, "render");
this.model.bind('change', this.render);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
can become
SomeView = Backbone.View.extend({
initialize: function(){
this.model.bind('change', this.render, this);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
See the documentation for on().
create model instance in the view
var myapp.myView = new View({
model: new Model
});
and when you initialize the Backbone.View inside add this .. which render will be called any time there is change is model attributes from it's defaults
this.model.bind('change', this.render,this)