Adding new Item to collection on 'add' triggers only once - backbone.js

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.

Related

How to filter a backbone collection and render the results

Total newbie to Backbone so apologize if this is a simple question.
I am have successfully loaded a collection and rendered the view. However I have a dropdown with A tags that I would like to use the filter the data displayed. I'm trying to set an event listener in my VIEW and then trigger a function within the view to filter the results and re-render the view.
Here's my code:
IBA.NewsModel = Backbone.Model.extend({});
IBA.NewsCollection = Backbone.Collection.extend({
model: IBA.NewsModel,
url: "/news/api"
});
IBA.NewsView = Backbone.View.extend({
el: '#main',
template: _.template($("#news-article").html()),
events: {
"change .dropdown-item": "filterNews"
},
initialize: function () {
this.collection = new IBA.NewsCollection();
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch({
success: function() {
console.log("JSON file load was successful");
view.render();
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
},
render: function () {
this.$el.html(this.template({
articles: this.collection.toJSON()
})
);
return this;
},
filterNews: function (e){
e.preventDefault();
var items = this.collection.where({cat_name: "interviews"});
console.log(items);
this.$el.html(this.template({articles: this.items.toJSON()}));
}
});
var view = new IBA.NewsView();
Easiest way to do it would be to reset the actual collection with the filtered results:
IBA.NewsModel = Backbone.Model.extend({});
IBA.NewsCollection = Backbone.Collection.extend({
model: IBA.NewsModel,
url: "/news/api"
});
IBA.NewsView = Backbone.View.extend({
el: '#main',
template: _.template($("#news-article").html()),
events: {
"change .dropdown-item": "filterNews"
},
initialize: function() {
this.collection = new IBA.NewsCollection();
this.listenTo(this.collection, 'reset', this.render);
this.fetchNews();
},
fetchNews: function() {
var view = this;
this.collection.fetch({
success: function() {
console.log("JSON file load was successful");
view.render();
},
error: function() {
console.log('There was some error in loading and processing the JSON file');
}
});
},
render: function() {
this.$el.html(this.template({
articles: this.collection.toJSON()
}));
return this;
},
filterNews: function(e) {
e.preventDefault();
this.collection.reset(this.collection.where({
cat_name: "interviews"
}));
}
});
var view = new IBA.NewsView();
When you want to go back to original data, fetch it again using the fetchNews() method.
You also had a syntax error that view was not defined in your initialize

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

Cannot read property 'toJSON' of undefined + backbone

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

Calling destroy on collection

I am doing a sample application similar to the Backbone-Todo. But when I am invoking destroy on collection it's giving error:
Uncaught TypeError: Cannot read property 'destroy' of undefined
How can I solve this problem. Please suggest.
Following is my method code:
$(function(){
var Todo = Backbone.Model.extend({
defaults: function() {
return {
title: "empty todo...",
order: Todos.nextOrder(),
done: false
};
}
});
var TodoList = Backbone.Collection.extend({
model : Todo,
localStorage: new Backbone.LocalStorage("todos-backbone"),
done: function() {
return this.where({done: true});
},
remaining: function() {
return this.without.apply(this, this.done());
},
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
comparator: 'order'
});
var TodoView = Backbone.View.extend({
tagName: "li",
template: _.template($('#item-template').html()),
events: {
"click a.destroy" : "clear"
},
initialize: function() {
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
clear: function(){
this.model.destroy();
}
});
var AppView = Backbone.View.extend({
el: $("#todoapp"),
statsTemplate: _.template($('#stats-template').html()),
events: {
"keypress #new-todo": "createOnEnter",
"click #remove-all": "clearCompleted"
},
initialize: function() {
this.input = this.$("#new-todo");
this.main = $('#main');
this.footer = this.$('footer');
this.listenTo(Todos, 'add', this.addOne);
this.listenTo(Todos, 'all', this.render);
Todos.fetch();
},
render: function() {
var done = Todos.done().length;
var remaining = Todos.remaining().length;
if (Todos.length) {
this.main.show();
this.footer.show();
this.footer.html(this.statsTemplate({done: done, remaining: remaining}));
} else {
this.main.hide();
this.footer.hide();
}
},
createOnEnter: function(e){
if(e.keyCode != 13) return;
if (!this.input.val()) return;
Todos.create({
title: this.input.val()
})
this.input.val('');
},
addOne: function(todo){
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
clearCompleted: function(){
_.invoke(Todos, 'destroy');
return false;
}
});
for this answer I assume Todos is an instance of TodoList. I also assume that your error is fired by this function in your AppView
clearCompleted: function(){
_.invoke(Todos, 'destroy');
return false;
}
In there you're trying to treat your Backbone.js Collection instance like what it is, a collection eg a list. But Backbone collections are not simply lists, they are objects that have the property models which is a list that contains all your models. So trying to use underscore's invoke (which works on lists) on an object is bound to cause errors.
But don't worry, Backbone neatly implements many Underscore methods for its Model and Collection, including invoke. This means you can invoke destroy for each model in a collection like this
SomeCollection.invoke('destroy');
Hope this helps!

Backbone.js iterating through JSON results from fetch()

I am very new to Backbone and just trying to figure out the way things work. How do I iterate through the "fetched" results?
(function($) {
console.log('Formcontainer init.');
var Field = Backbone.Model.extend({
defaults: {
id: "0",
memberlist_id: "3",
field_name: "sample-field-name",
},
initialize: function() {
console.log('Field model init.');
}
});
var FieldCollection = Backbone.Collection.extend({
defaults: {
model: Field
},
model: Field,
url: 'http://localhost:8888/getstuff/3.json',
initialize: function() {
console.log('FieldCollection init.');
}
});
var ListView = Backbone.View.extend({
el: '#app-container',
initialize: function() {
_.bindAll(this,"render");
console.log('ListView init.')
this.counter = 0;
this.collection = new FieldCollection();
this.collection.fetch();
//this.collection.bind('reset', function() { console.log('xxx')});
//this.collection.fetch();
this.render();
},
events: {
'click #add': 'addItem'
},
render: function() {
var self = this;
console.log('Render called.');
},
addItem: function() {
this.counter++;
this.$("ul").append("<li>hello world" + this.counter + "</li>");
}
});
var listView = new ListView();
})(jQuery);
I get this in my Firebug console:
Formcontainer init.
ListView init.
FieldCollection init.
GET http://localhost:8888/getstuff/3.json 200 OK
Render called.
Field model init.
Field model init.
Field model init.
Field model init.
Field model init.
It seems that the fetch() was called - since "Field model init." is in the console 5 times. But how to I output that? I would want to append the items in an unordered list.
Thanks!
Bind the reset event to your render function...
this.collection.bind('reset', this.render, this);
This gets triggered after the fetch is complete, so you can create the list...
render: function() {
var self = this;
console.log('Render called.');
this.collection.each(function(i,item) {
this.$el.append("<ul>" + item.get("field_name") + "</ul>");
});
},

Resources