Backbone View property returning undefined - javascript

Here is my view:
define(
[
"jquery"
, "underscore"
, "backbone"
, "eventView"
]
, function($, _, Backbone, EventView) {
"use strict";
var TimelineView = Backbone.View.extend({
tagName: 'div'
, className: 'column'
, _EventViews: {} // Cache event views for reuse
, initialize: function() {
this.collection.bind('add', this.add);
this.collection.bind('reset', this.add);
}
, render: function() {
return this;
}
// Listen for additions to collection and draw views
, add: function(model) {
var eventView = new EventView({
model: model
});
// Cache the event
console.log(this._EventViews);
this._EventViews[model.get('id')] = eventView;
// Draw event
eventView.render();
}
});
return TimelineView
}
);
As you can see I set the _EventViews property to contain an empty object. However when I call the add() function console.log(this._EventViews) returns undefined and the following statement fails.
Can anyone tell me why this is?

The problem is that within add, this is not your TimelineView. See this article for an explanation of context in javascript: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/this
You can solve this in a few different ways. The easiest in this situation is to use the third parameter of bind or on (these two are the same).
initialize: function() {
this.collection.on('add', this.add, this);
this.collection.on('reset', this.add, this);
}
Or use listenTo instead.
initialize: function() {
this.listenTo(this.collection, 'add', this.add);
this.listenTo(this.collection, 'reset', this.add);
}
Also, your _EventViews cache will be shared by all instances of TimelineView. If that is not what you want, create it in initialize instead.
initialize: function() {
this._EventViews = {};
this.listenTo(this.collection, 'add', this.add);
this.listenTo(this.collection, 'reset', this.add);
}

It works for me:
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone.js"></script>
<script>
var TimelineView = Backbone.View.extend({
tagName: 'div'
, className: 'column'
, _EventViews: {} // Cache event views for reuse
, initialize: function() {
//this.collection.bind('add', this.add);
//this.collection.bind('reset', this.add);
}
, render: function() {
return this;
}
// Listen for additions to collection and draw views
, add: function(model) {
var eventView = ({
model: model
});
// Cache the event
console.log(this._EventViews); // Prints: Object {}
this._EventViews[model.get('id')] = eventView;
// Draw event
eventView.render();
}
});
var a = new TimelineView();
a.add();
</script>
I think the problem is the .add() method is invoked from the collection add event. When you add a listener (with in backbone is done with the .bind() function) you must bind (on the native meaning) the function:
_.bindAll(this, 'add');
OR
this.add = this.add.bind(this);
You have to do it before you add the function as a listener:
initialize: function() {
_.bindAll(this, 'add');
this.collection.bind('add', this.add);
this.collection.bind('reset', this.add);
}

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

How to improve my Backbone code

I'm starting to develop in Backbone.js.
And I still do not know how to write code using best practices.
My app is working but I have some issues, for example, the comparator does not work.
I would really help to make him better.
View:
Todos.Views.TasksView = Backbone.View.extend({
template: JST['todos/index'],
tagName: 'div',
id: '#todos',
initialize: function () {
this.collection = new Todos.Collections.TasksCollection();
this.collection.fetch({
reset: true
});
this.render();
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this.collection, 'change', this.render);
this.listenTo(this.collection, 'sort', this.render);
},
render: function () {
$(this.el).html(this.template({
tasks: tasks[0].task
}));
return this;
}
});
Collection:
Todos.Collections.TasksCollection = Backbone.Collection.extend({
model: Todos.Models.TasksModel
url: "/api/tasks.json",
comparator: function (sort) {
return -(new Date(sort.get('eventTime'))).getTime();
},
parse: function (response) {
return response;
}
});
Model:
Todos.Models.TasksModel = Backbone.Model.extend({
parse: function (response, options) {
return response;
}
});
Router:
Todos.Routers.TasksRouter = Backbone.Router.extend({
routes: {
'': 'index'
},
initialize: function () {
var viewTasks = new Todo.Views.TasksView({
model: Todos.Models.TasksModel,
el: '.tasks-list'
});
}
});
App.js:
window.Todos = {
Models: {},
Collections: {},
Views: {},
Routers: {},
initialize: function () {
new Todos.Routers.TasksRouter();
Backbone.history.start({})
},
$(document).ready(function () {
Todos.initialize();
});
Inside your views try to not have model or collection function like fetch or save or set.This is not a view responsibility.
Example : Instead of have this.collection.fetch() inside your collection, do something like this.
this.collection = new Todos.Collections.TasksCollection();
this.collection.getSomething();
this.render();
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this.collection, 'change', this.render);
this.listenTo(this.collection, 'sort', this.render);
Inside your collection, add a method to perform "fetch()" request.
I don't understand why you're declaring the parse function.. you aren't doing anything..
I like to use parse function when I want change something that is in your JSON before backbone bind it to your model/collection.
I can't see where you're calling the comparator function.. maybe the 'sort' parameter is with an invalid value.
Inside your model you're using parse again.. I can't see a reason.
Your router is ok.
I like your code.. for a beginner as you said. it's really good.
I recommend you use lo-dash instead of underscore... It have more options to work with arrays.
Try to use require.js to load your js dependencies.. It will help you a lot.
www.requirejs.org
Hope it helps.

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

listenTo on collection reset doesn't get invoked after collection is fetched

Below is my initialization function of a view that should receive a module name when initialized in order to fetch the correct data from the collection.
The issue is:
Listento doesn't redirect to the render() method after the collection is fetched, also it gives me an error on the console
TypeError: e is undefined
What is the mistake i made with the code below??
initialize: function() {
var that = this;
if(this.options.module === 'questions'){
require([
'app/collections/questions'
], function(QuestionsCollection){
var moduleCollection = new QuestionsCollection();
that.collection = moduleCollection;
moduleCollection.fetch({
reset: true,
success: function(){},
error: function(){}
});
});
}
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(Backbone, 'close:Home', this.close);
},
I believe this is a scope issue where the require module is in a closure. Try the following:
initialize: function() {
var that = this;
if(this.options.module === 'questions'){
require([
'app/collections/questions'
], function(QuestionsCollection){
var moduleCollection = new QuestionsCollection();
moduleCollection.fetch({
reset: true,
success: function(){},
error: function(){}
});
that.listenTo(moduleCollection, 'reset', that.render);
});
}
this.listenTo(Backbone, 'close:Home', this.close);
},
You problem is riquire.js, when you write require([/*jsFile*/], callback); the call to callback is made asynchronously after the jsFile is loaded. So when you call require([... immediatly after that you call this.listenTo(this.collection, 'reset', this.render); and at the time this last line is executed the jsFile is not yet loaded and the callback is not yet called thus the this.collection is undefined.
Take a look at this example http://jsfiddle.net/ZZuGC/1/ and the order the console.log('require'); & console.log('listenTo'); are printed to the console.
I think that the solution to your problem is as follows :
initialize: function() {
var that = this;
if(this.options.module === 'questions'){
require([
'app/collections/questions'
], function(QuestionsCollection){
var moduleCollection = new QuestionsCollection();
that.collection = moduleCollection;
// here is my suggestion
that.listenTo(that.collection, 'reset', that.render);
moduleCollection.fetch({
reset: true,
success: function(){},
error: function(){}
});
});
}
this.listenTo(Backbone, 'close:Home', this.close);
},
I have updated the example http://jsfiddle.net/ZZuGC/2/

Collection add event listener in Backbone

I am trying to update my view whenever I add a new model to my collection. My first question is do I automatically add a model to my collection when I save that model, like:
PostsApp.Views.Form = Backbone.View.extend({
template: _.template($('#form-template').html()),
render: function(){
this.$el.html(this.template(this.model.toJSON()));
},
events:{
'click button' : 'save'
},
save: function(e){
console.log("is this working");
e.preventDefault();
var newname = this.$('input[name=name-input]').val();
var newadress = this.$('input[name=adress-input]').val();
this.model.save({name: newname, adress : newadress});
}
});
or do I still have to do collection.add()
Other than that to see the new model in my view I am trying to add an 'add' event listener like this:
PostsApp.Views.Posts = Backbone.View.extend({
initialize: function(){
this.collection.on('add', this.addOne, this);
},
render: function(){
this.collection.forEach(this.addOne, this);
},
addOne: function(post){
var postView = new PostsApp.Views.Post({model:post});
postView.render();
this.$el.append(postView.el);
}
});
This not only doesnt work, but when I add the initialize method, it just duplicates everything in my model when the page is first loaded.
Nope.. When you do a model.save , it will just create a zombie model ( If it not already a part of the collection .i.e If a New model is saved) which is not a part of any collection.
So your add event will not be triggered for the collection.
If you want the add event to be triggered , Use the create method of collection , which then will know on which collection the new model has to be added..
collection.create({model});
Then it would internally add the model to the collection and fire the add event
Also it is a better idea to use listenTo instead of attaching events using on
this.listenTo(this.collection, 'add', this.addOne);
Code
PostsApp.Views.Form = Backbone.View.extend({
template: _.template($('#form-template').html()),
render: function () {
this.$el.html(this.template(this.model.toJSON()));
},
events: {
'click button': 'save'
},
save: function (e) {
console.log("is this working");
e.preventDefault();
var newname = this.$('input[name=name-input]').val();
var newadress = this.$('input[name=adress-input]').val();
this.collection.create({
name: newname,
adress: newadress
});
}
});
PostsApp.Views.Posts = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, 'add', this.addOne);
},
render: function () {
this.collection.forEach(this.addOne, this);
},
addOne: function (post) {
var postView = new PostsApp.Views.Post({
model: post,
collection : this.collection
});
postView.render();
this.$el.append(postView.el);
}
});

Categories