View.stopListening() not working - backbone.js

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/

Related

Backbone ReferenceError

I have Backbone view that looks like this:
App.Views.HistoricalDataSelector = Backbone.View.extend({
initialize: function (options) {
this.template = JST['backbone/templates/historical_data_selector'];
this.collection = options.collection;
this.model = options.model;
this.render();
},
myCollection: function() {
return this.collection.byReportType(this.model.get('report_type')).byReportOrganizationType(this.model.get('report_organization_type'))
},
render: function () {
this.$el.html(this.template({
collection: myCollection,
model: this.model
}));
}
});
when I try to render it Backbone returns me the following error:
ReferenceError: myCollection is not defined
But when I change render method to this:
render: function () {
this.$el.html(this.template({
collection: this.collection.byReportType(this.model.get('report_type')).byReportOrganizationType(this.model.get('report_organization_type')),
model: this.model
}));
}
why he can't find this myCollection method?
You forgot the this keyword, and probably also to invoke the method:
render: function () {
this.$el.html(this.template({
collection: this.myCollection(),
//-----------^ view reference ^---- function invocation ------
model: this.model
}));
}

backbone.js iterate a collection

I have set up a collection for Logs. The api returns the results as JSON. I saw a previous topic where it was suggested to add the parse method on the collection. Having done so, when I execute the code I am not getting any output to the console. Nevertheless, I am new to Backbone so any insight and/or guidance would be appreciated. My understanding of collection.each may not be correct.
var Log = Backbone.Model.extend({});
var LogList = Backbone.Collection.extend({
model: Log,
url: 'api/logs',
parse: function(response) {
return response.logs;
}
});
var LogListView = Backbone.View.extend({
el: $('#logs-list'),
initialize: function() {
this.collection = new LogList();
this.collection.fetch();
this.render();
},
render: function() {
this.collection.each(function(log) {
console.log('log item.', log);
});
}
});
$(document).ready(function(){
console.log('ready.');
new LogListView();
});
Fetch is asynchronous. Rewrite your code to call render with a callback:
var LogListView = Backbone.View.extend({
el: $('#logs-list'),
initialize: function() {
var self = this;
this.collection = new LogList();
this.collection.fetch().done(function(){
self.render();
});
},
render: function() {
this.collection.each(function(log) {
console.log('log item.', log);
});
}
});

Pass 'this' reference to Backbone function

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

Backbone - Collection fetch returns undefined?

I have an application using backbone but whenever I call the fetch() method for the collection it returns undefined:
// App
(function () {
window.app = {};
app.collections = {};
app.models = {};
app.views = {};
$(function () {
app.collections.complaintTypes = new app.collections.ComplaintTypesCollection();
app.views.complaintTypesView = new app.views.ComplaintTypesView({ collection: app.collections.complaintTypes });
});
})();
// Collections
(function (collections, model) {
collections.ComplaintTypesCollection = Backbone.Collection.extend({
initialize: function () {
this.fetch();
},
model: model,
url: '/api/ComplaintTypes'
});
})(app.collections, app.models.ComplaintType);
// Models
(function (models) {
models.ComplaintType = Backbone.Model.extend({
idAttribute: 'ComplaintTypeId'
});
})(app.models);
// Views
(function (views) {
views.ComplaintTypesView = Backbone.View.extend({
initialize: function () {
this.collection.on('reset', this.render, this);
},
render: function () {
console.log(this.collection);
}
});
})(app.views);
But this doesn't return anything? If I use fiddler and go to my URL: /api/ComplaintTypes I do retrieve data back so I'm not sure what Im doing wrong here?
Deleting "model" line in the collection file worked for me.
Fetch is async. If you need to get the results, you need to pass a "success" handler, i.e.:
function myHandler(models) {
}
...
this.fetch({success: myHandler});
I think the problem is that you create the view while the collection has not been fetched yet (because of JS's asynchronous behavior). Try this way:
$(function () {
app.collections.complaintTypes = new app.collections.ComplaintTypesCollection();
app.collections.complaintTypes.fetch({success:function(){
app.views.complaintTypesView = new app.views.ComplaintTypesView({ collection: app.collections.complaintTypes });
}});
});
And remove the initialize function from your collection.

Is there a way to bind a Model's .change() trigger to a View's .render() function without creating more than one?

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)

Resources