Cannot read property 'toJSON' of undefined + backbone - backbone.js

I am really new to backbone, and in a world of confusion behind it. Right now I am trying to render a view of a single person, preferably without iterating through the entire collection, using the collection.get() method.
Here is the user collection:
App.Collections.UserCollection = Backbone.Collection.extend({
url: "/users",
model: App.Models.User,
initialize: function(){
console.log('users collection');
},
});
The user model:
App.Models.User = Backbone.Model.extend({
rootURL: '/users',
initialize: function(){
console.log('User model being generated');
}
});
the Users View (all users):
App.Views.Users = Backbone.View.extend({
el: "body",
model: 'user',
initialize: function(){
console.log('User view rendering');
this.collection.fetch();
this.listenTo(this.collection, 'reset', this.addAll);
},
clearDiv: function(){
var container = $('#render-area');
$('body').removeClass('homepage');
$('#main-nav').fadeIn(100);
container.empty();
this.addUser();
},
addUser: function(){
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
$('#render-area').append(user.el);
},
events: {
'click #view-profile' : 'clearDiv'
}
});
and the viewUser view (single user):
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.render();
},
render: function(){
$('#render-area').html(this.template(this.model.toJSON()));
},
});
I thought that I was grabbing the single user model in the Users view, and passing it to the ViewUser view with these lines
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
But it seems that I am not. Any help and/or explanation is appreciated, thanks!

did you check the this.collection.get is return an object?
Maybe it can not fetch the currentUser?
before render the view you should
a) check the model is a valid model before calling the view and render a "not found" view or
b) in the view check if the model is valid and render a "not found" view
c) or you can handle it in handlebars template
Example A (untested):
var current_user_model = this.collection.get(currentUser);
if (current_user_model) {
var user = new App.Views.ViewUser({ model: current_user_model });
} else {
new AppViews.ViewUserNotFound();
}
Example B :
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.templateNotFound = HandlebarsTemplates['user_profile_not_found'];
this.render();
},
render: function(){
if(this.model) {
$('#render-area').html(this.template(this.model.toJSON()));
} else {
$('#render-area').html(this.templateNotFound());
}
},
});
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
Example C:
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.templateNotFound = HandlebarsTemplates['user_profile_not_found'];
this.render();
},
render: function(){
if(!this.model) {
this.model = new this.model(); // create model with defaults
}
$('#render-area').html(this.template(this.model.toJSON()));
},
});
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
all examples untested..
hope this helps a little bit,
best Carsten

I would do that:
App.Views.Users = Backbone.View.extend({
el: "body",
model: 'user',
initialize: function(){
console.log('User view rendering');
this.listenTo(this.collection, 'reset', this.addAll);
},
clearDiv: function(){
var self = this;
this.collection.fetch({
success: function() {
var container = $('#render-area');
$('body').removeClass('homepage');
$('#main-nav').fadeIn(100);
container.empty();
self.addUser();
}
});
},
addUser: function(){
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
$('#render-area').append(user.el);
},
events: {
'click #view-profile' : 'clearDiv'
}
});

Related

Backboe memory leak in subviews

I still strugling with memory leak in my app. I wannted to do it without huge changes in code.
var ItemsView = Backbone.View.extend({
id:'products', // If I change it to el: document.getElementById('products') and without passing views into items object, my views are properly rendered but with memory leak
events: { },
initialize: function() {
_.bindAll(this);
this.listenTo(this.collection, 'reset', this.reset);
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'change', this.changeItem);
this.listenTo(this.collection, 'destroy', this.delItem);
this.items = [];
},
reset: function(){ console.log("reset");
this.el.innerHTML = null;
this.render();
},
render: function(){
for(var i=0; i < this.collection.length; i++){
this.renderItem(this.collection.models[i]);
}
},
renderItem: function( model ){
var itemView = new ItemView({ model: model });
itemView.render();
this.items.push(itemView);
jBone(this.el).append(itemView.el);
},
addItem: function(){ console.log("addItem");
this.renderItem();
},
changeItem: function(){ console.log("changeItem"); },
delItem: function(){ console.log("delItem"); },
remove: function() {
_.invoke(this.items, 'remove');
this.items = [];
Backbone.View.prototype.remove.call(this);
}
});
return ItemsView;
This is my Itemsview it is executed when user hit orderview, there is created ItemView for every model in collection:
var ItemView = Backbone.View.extend({
tagName: "li",
className: "productcc",
initialize: function () {
_.bindAll(this, 'addItem', 'removeItem', 'updateItem');
this.listenTo(this.model, 'remove', this.removeItem);
this.listenTo(this.model, 'change', this.updateItem);
},
events: {},
render: function () {
var model = this.model.toJSON();
this.el.innerHTML += '<div class="tabody"><h4 class="tablename">'+model.title+'<h4>'+model.status+'</div>';
return this;
},
addItem: function(){
this.collection.create({"table_no":"demo"});
},
changeItem: function(e){
e.preventDefault();
this.model.save({ table_no: 'demo' });
},
updateItem: function(newstuff){
console.log("updateItem");
console.log(this.el);
},
delItem: function(){
this.model.destroy({ silent: true });
},
removeItem: function(model){
console.log("removeItem");
console.log(model);
var self = this;
self.el.remove();
}
});
return ItemView;
MY ROUTER:
var AppRouter = Backbone.Router.extend({
routes: {
'' : 'home',
'home' : 'home',
'customer/:customer_id': 'showItems'
}
});
var initialize = function(options) {
window.app_router = new AppRouter;
window.socket = io.connect('www.example.com');
this.socketOrdersCollection = new SocketOrdersCollection();
this.ordersView = new OrdersView({ collection: this.socketOrdersCollection });
this.socketOrdersCollection.fetch({ reset: true });
app_router.on('route:home', function() { });
app_router.on('route:showItems', function(customer_id) {
if (this.itemsView) {
this.itemsView.remove();
}
this.socketItemsCollection = new SocketItemsCollection();
this.socketItemsCollection.fetch({ data: { id: customer_id}, reset: true });
this.itemsView = new ItemsView({
collection: this.socketItemsCollection,
model: { tableName: customer_id }
});
});
Backbone.history.start();
};
I have to remove also ItemsView after click to another order...
Thanks for any opinion.
Ok. Let me take a stab at what you're attempting here.
var ItemsView = Backbone.View.extend({
el: document.getElementById('products'),
events: { },
initialize: function() {
// everything you had before
this.items = [];
},
// etc.
renderItem: function( model ){
var itemView = new ItemView({ model: model });
itemView.render();
this.items.push(itemView);
jBone(this.el).append(itemView.el);
},
// etc.
// we're overloading the view's remove method, so we clean up our subviews
remove: function() {
_.invoke(this.items, 'remove');
this.items = [];
Backbone.View.prototype.remove.call(this);
}
});
return ItemsView;
And then in the router:
var initialize = function(options) {
// etc.
app_router.on('route:home', function() { });
app_router.on('route:showItems', function(customer_id) {
if (this.itemsView) {
this.itemsView.remove();
}
// everything else the same
});
Backbone.history.start();
};
So now, your ItemsView will clean up any child items it has, and when you change customers, you'll clean up any ItemsView you have open before generating a new one.
EDIT
I see what you're having a problem with now.
In your route handler, you're going to need to do something along these lines:
app_router.on('route:showItems', function(customer_id) {
// everything you already have
jBone(document.getElementById('container')).append(this.itemsView);
});

Adding new Item to collection on 'add' triggers only once

I am pretty new to backbone,so probably it is stupid bug.
When I press send button(#send_email_button) , one email is rendered as it should,
but when i press it again, no more emails added.
the only logs i got is:(after second+ push)
:
console.log('adding to collection');
console.log('about to exit');
in other words it does not even enters add handler in collection.
Can someone explain why and how to fix this?
Many thanks!
EDIT: If i delete 1 email that rendered , and press send again, new email added correctly.
Here relevant code:
$(document).ready(function() {
//email model
var EmailModel = Backbone.Model.extend({
defaults: {
id: '',
email_from: '',
email_recipient: '',
email_subject: '',
email_data: '',
is_email_read: '',
email_date: ''
}
});
//email collection
var email_collection = Backbone.Collection.extend({
model: EmailModel,
url: '/fetch_received_emails'
});
var email_collection = new email_collection();
var EmailView = Backbone.View.extend({
model: new EmailModel(),
tagName:'li',
events: {
"click #email_template_view" : "click_email"
},
initialize: function() {
console.log('initializing email view');
this.template = _.template($('#email_template').html());
console.log('finish initializing email view');
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
click_email: function() {
this.model.is_email_read = true;
$('#toggle_part_email').toggleClass('no_display');
},
});
var CollectionView = Backbone.View.extend({
model: email_collection,
el: $('#emails_class'),
initialize: function() {
console.log('init to collection view');
this.model.fetch();
this.render();
this.model.on('change', this.render, this);
this.model.on('add', this.render, this);
this.model.on('remove', this.render, this);
},
render: function(){
console.log('rendering collection');
var that = this,
i;
that.$el.html('');
emails = this.model.toArray();
for (i in emails){
console.log(' printing emails');
console.log(emails[i]);
var new_email_view = new EmailView( {model : emails[i]});
that.$el.append(new_email_view.render().$el);
}
console.log('about to exit collection view');
return this;
}
});
$('#send_email_button').click(function(event){
// event.preventDefault();
var sending_date= new Date();
sending_date = sending_date.toDateString()
//new email to ajax
console.log('adding to collection');
email_collection.add(new EmailModel({
'email_from':$('#email_from').val(),
'email_recipient' :$('#email_recipient').val(),
'email_subject': $('#email_subject').val(),
'email_data':$('#email_data').val(),
'is_email_read':false,
'email_date': sending_date
}));
console.log('about to exit');
return false;
});
//create singelton for the collection view
var c = new CollectionView();
});
Why don't you try to use the click event as the other one? Inside the collectionView use events again.
events: {
"click #send_email_button" : "AnyNameThatYouWant"
},
AnyNameThatYouWant: function() {
//Do all the things
},
Try this.

Backbone appends to list view instead of replacing it

I am producing a single page website with Wordpress and Backbone.js, i have come into a problem with when i fetch new data. It simply adds DOM elements onto the container el rather than replacing them. The collection updates correctly as i can see it has the right amount of elements in the console.
var PostItem = Backbone.Model.extend();
var PostItems = Backbone.Collection.extend({
model: PostItem,
url: '/wp-admin/admin-ajax.php'
});
var postItems = new PostItems();
var PostView = Backbone.View.extend({ /* Model View */
tagName : 'article',
className : 'widget',
template : _.template( $('#widgetPost').html() ),
render: function(){
var attributes = this.model.toJSON();
this.$el.html( this.template( attributes ) );
return this;
}
});
var PostListView = Backbone.View.extend({ /* Collection View */
el : '#content',
initialize: function(){
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
},
addOne: function(postItem){
var postView = new PostView({ model : postItem });
this.$el.append( postView.render().el );
},
addAll: function(){
this.collection.forEach(this.addOne, this);
},
render: function(){
this.addAll();
},
});
var postListView = new PostListView({
collection : postItems
});
$(function(){
$('a#posts').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_posts'
}
});
return false;
});
$('a#pages').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_pages'
}
});
return false;
});
});
You need to clear out your collectionView's $el! :)
addAll: function(){
this.$el.empty();
this.collection.forEach(this.addOne, this);
}
This should do the trick.

Backbone.js Collections View forEach doesn't work

I am trying to use collections to list my data coming from my api.
But the problem is, when I use forEach, the function that I called (addOne) doesn't run.
There is also something I suspect working wrong. Should my collection save the returning JSON under the models like that?
Object -> models -> 0 -> attributes -> ...
My View:
s.Views.Fs = Backbone.View.extend({
className: "",
template: _.template("<%= name %>"),
initialize: function() {
},
render: function() {
this.collection.forEach(this.addOne, this);
},
addOne: function(f) {
alert("a");
var fV = new s.Views.PF({model: f});
this.$el.append(fV.render().el);
}
});
My Collection:
s.Collections.FL = Backbone.Collection.extend({
url: "api/fs/",
model: s.Models.F,
});
My Model:
s.Models.F = Backbone.Model.extend( {
urlRoot: 'api/fs/',
defaults: {
...
},
...
parse: function(response) {
return response;
},
});
My Route (And App):
var sApp = new (Backbone.Router.extend({
f_a: function() {
this.fL= new s.Collections.FL();
this.fLV= new s.Views.Fs({collection: this.fL});
this.fL.fetch();
this.fLV.render();
},
});
Listening for events is made by this.collection.on('add', this.addOne, this); under collection's view. Here is tested code's summary (Thanks for the tip 'mu is too short'):
VIEW
s.Views.Fs = Backbone.View.extend({
className: "",
template: _.template("<%= name %>"),
initialize: function() {
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.render, this);
},
render: function() {
this.collection.forEach(this.addOne, this);
},
addOne: function(f) {
var fV = new s.Views.PF({model: f});
fV.render();
this.$el.append(fV.el);
}
});
COLLECTION
s.Collections.FL = Backbone.Collection.extend({
url: "api/fs/",
model: s.Models.F,
});
MODEL
s.Models.F = Backbone.Model.extend( {
urlRoot: 'api/fs/',
// No need to parse here.
});
ROUTER
var sApp = new (Backbone.Router.extend({
f_a: function() {
this.fL= new s.Collections.FL();
this.fLV= new s.Views.Fs({collection: this.fL});
this.fLV.render();
$("#content").html(this.fLV.el);
this.fL.fetch();
},
});

backbone.js events not firing after re-render even with delegateEvents()

I have a View whit nested views inside that when rendered has a link on it with a click event attached. First time I click on al link, all works. But when re-render all views, links events are disappeared. I tried forcing delegateEvents() in render and remove views before re-render but nothing.
Here is my code:
var SlideView = Backbone.View.extend({
tagName: 'li',
events: {
'click .nested':'destroy'
},
template: _.template($('#slides-nested-template').html()),
render: function(e){
var _el = $(this.el);
_el.html(this.template(this.model.toJSON()));
this.delegateEvents(this.events);
return this;
},
destroy: function(e){
e.preventDefault();
Presentations.eliminate($(e.target).attr('rel'));
}
});
var SectionView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#slides-template').html()),
events: {
'click .no-nested':'destroy'
},
initialize: function(options){
this.views = [];
_.bindAll(this, 'destroy');
},
render: function(){
var _el = $(this.el), self=this;
_el.html(this.template(this.model.toJSON()));
_ul = this.$('ul');
var data = Presentations.filter(function(slide){
return slide.get('padre') == self.model.get('id');
});
_.each(data, function(slide){
view = new SlideView({
model: slide
});
self.views.push(view);
_ul.append(view.render().el);
});
this.delegateEvents(this.events);
return this;
},
destroy: function(e){
e.preventDefault();
Presentations.eliminate($(e.target).attr('rel'));
},
removeViews: function(){
while (this.views.length){
var view = this.views.shift();
view.undelegateEvents();
view.remove();
view = null;
}
}
});
var SectionList = Backbone.View.extend({
tagName: 'ul',
className: 'sortable',
initialize: function(options){
Presentations.on('add', this.render, this);
Presentations.on('reset', this.render, this);
Presentations.on('remove', this.render, this);
this.views = [];
},
render: function(e){
//this.removeViews();
$('#slides .content').empty();
var data = Presentations.filter(function(slide){
return slide.get('padre') === false;
});
var view, self=this, _el=$(this.el);
_.each(data, function(slide){
view = new SectionView({
model: slide
});
_el.append(view.render().el);
self.views.push(view);
});
$('#slides .content').append(_el);
return this;
},
removeViews: function(){
while (this.views.length){
var view = this.views.shift();
view.undelegateEvents();
view.removeViews();
view.remove();
view = null;
}
}
});
var Presentation = Backbone.Model.extend({
});
var PresentationList = Backbone.Collection.extend({
model: Presentation,
url: BASE_URL+'api/slides/'+PRESENTATION,
eliminate: function(id){
this.each(function(slide){
if (slide.get('padre')==id){
slide.set('padre', false);
slide.save();
}
});
var ref = this.get(id);
ref.destroy();
this.remove(ref);
}
});
Presentations = new PresentationList();
SectionListView = new SectionList();
Presentations.fetch();

Resources