backbone.js: add item to collection right after current item - backbone.js

I'm trying to create a invoice rows list with backbone and one of the buttons/events is that when You click "add" on an existing row, it should add a new row right below the one that's "add" button was clicked.
First, I am not sure if I should handle this event in the InvoiceItemView or InvoiceItemListView, and second, I am not sure how to do this. Can anybody help me?
I tried this:
$(function(){
// Invoice row
var InvoiceItem = Backbone.Model.extend({
// Set default values
defaults : {
name: "",
quantity: 1,
price: "",
total: ""
},
// Ensure each item has at least a quantity of one
initialize: function() {
this.set({ "quantity" : this.defaults.quantity });
},
// Delete item (row) from invoice
clear: function() {
this.destroy();
}
});
// Collection of Invoice Items ( rows )
var InvoiceItemList = Backbone.Collection.extend({
// Colelction's model
model: InvoiceItem,
// Use localStorage to save data
localStorage: new Store("invoices-backbone"),
// Calculate invoice totals
calculate_totals: function() {
alert('arvutus');
},
// Generate next item order number
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
}
});
// Create a global colelction of Invoice Rows
var InvoiceItems = new InvoiceItemList;
// Invoice Item View
var InvoiceItemView = Backbone.View.extend({
// it's a tr tag
tagName: 'tr',
// Cache the template
template: _.template($('#invoiceitem-template').html()),
events: {
"click .remove" : "clear",
"click .add" : "addAfter"
},
initialize: function() {
_.bindAll(this, 'render', 'addAfter', 'remove');
this.model.bind('change', this.render);
this.model.bind('destroy', this.remove);
this.render();
},
// Render the contents
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
$(this.el).attr('id', 'item-' + this.model.get('order'));
return this;
},
// Remove and destroy the item
clear: function() {
this.model.clear();
},
// Add new item
addAfter: function(ItemId) {
var view = new InvoiceItemView({model: InvoiceItem});
this.$("tbody tr#item"+ItemId).after(view.render().el);
}
});
// Invoice Item List View
InvoiceItemListView = Backbone.View.extend({
// Bind to existing DOM element
el: $("#items"),
// Template for displaying totals
totalsTemplate: _.template($('#totals-template').html()),
// Kick-off (load from localStorage as well)
initialize: function() {
_.bindAll(this, 'addOne', 'render');
InvoiceItems.bind('add', this.addOne);
InvoiceItems.fetch();
InvoiceItems.create(this.newAttributes());
InvoiceItems.create(this.newAttributes());
InvoiceItems.create(this.newAttributes());
this.render();
},
// Re-render the totals
render: function() {
this.$('#totals').html(this.totalsTemplate({
total: this.total
}));
},
// Generate the attributes for a new Invoice Item.
newAttributes: function() {
return {
name: '',
price: '',
total: '',
order: InvoiceItems.nextOrder()
};
},
// Add a single invoice item to the list
addOne: function(InvoiceItem) {
var view = new InvoiceItemView({model: InvoiceItem});
this.$("tbody").append(view.render().el);
}
});
var iView = new InvoiceItemListView;
});
But I am getting an error: this.model.toJSON is not a function

The problem is where you create your new InvoiceItemView. You have to create a new instance of InvoiceItem
// Add new item
addAfter: function(ItemId) {
var view = new InvoiceItemView({model: new InvoiceItem()});
this.$("tbody tr#item"+ItemId).after(view.render().el);
}

Related

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.

Multiple Views and Sub Views with 1 collection in Backbone

I have issue in rendering Shopping Bag Views using Backbone for my website.
I am using 1 collection for all Bag Views (“Quick_View” of items list & “Normal_View” of Items list). Also I created “Item_View”, which is being used to render each item in both the Views.
It is a SPA (Single Page Application) and “Quick_View” is initiated and rendered for all Backbone routes and hidden by default. Whenever user clicks on “Quick_View” link from any page it is showing. There is no route defined for it.
The “Normal_View”, can be accessed using Checkout button given in “Quick_View”. It is bind with “domain/checkout” route.
When I access “Normal_View” from “Quick_View” check button; it works fine and both (Quick and Normal) views are in Sync. Means, when we add, delete, update any item in any of the View, both Views are getting updated accordingly.
But when I access “domain/checkout” route directly in a new browser, both views are getting rendered fine, but they are not in sync. Means, change in 1 view does not update another view.
The reason, I tracked is, when I access “Normal_View” through “Quick_View”, model for each item in both the Views having same CID, so the both Views are in sync, if there is any change in a model from any of the View.
And, when I access “Normal_View” directly, model for each item in both the views are not having same CID, so they do not work as expected.
There are few more points to consider:
Collection is firing reset event twice for “Quick_View” and each item
in “Quick_View” is rendering twice.
When, I access “Normal_View” (in either way), “Quick_View” is again getting rendered but once “Normal_View” rendering is over.
// Main View
var mainView = Backbone.View.extend({
el: 'body',
template: {
header: Handlebars.compile(headerTemplate),
mainNav: Handlebars.compile(mainNavtemplate),
footer: Handlebars.compile(footerTemplate)
},
initialize: function() {
_.bindAll();
AW.collection.bag = new bagCollection();
//AW.collection.bag.fetch({reset:true});
},
render: function() {
this.$el.html(this.template());
this.loadSubView('bagQV');
},
loadSubView: function(subView) {
switch(subView) {
case 'home' :
if(!AW.view.home) AW.view.home = new homepageView();
AW.view.home.render();
break;
case 'bagQV' :
if(!AW.view.bagQV) AW.view.bagQV = new bagQuickView({collection: AW.collection.bag});
//AW.view.bagQV.render();
break;
case 'checkout' :
if(!AW.view.checkout) AW.view.checkout = new checkoutView({collection: AW.collection.bag});
AW.view.checkout.render();
break;
}
}
});
// Single Item View
var bagItemView = Backbone.View.extend({
tagName: 'tr',
template: Handlebars.compile(bagItemTemplate),
initialize: function() {
_.bindAll(this);
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'remove', this.removeItem);
$(document).on('keyup', this.listenKeyboard);
},
events: {
'click .qtyInput .button' : 'updateItem',
'click .controls a.remove' : 'removeModel'
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.attr('data-id',this.model.cid);
return this;
},
updateItem: function(e) {
e.preventDefault();
e.stopPropagation();
var newQty = this.$el.find('.qtyInput input').val();
var newAmt = newQty * parseFloat(this.model.get('prodRate').replace('$',''));
this.model.set({prodQty: newQty, amount: '$' + newAmt});
this.cancelEdit(e);
},
removeModel: function(e) {
e.preventDefault();
e.stopPropagation();
if(AW.collection.bag) AW.collection.bag.remove(this.model);
},
removeItem: function() {
this.$el.remove();
}
});
// Bag Quick View
var bagQuickView = Backbone.View.extend({
tagName: 'div',
id: 'myBagQV',
template: Handlebars.compile(bagQuickViewTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
if($('#myBagQV').length == 0) {
this.$el.html(this.template());
$('body').append(this.el);
}
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
$('#itemsInBag table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
$('#itemsInBag table tbody').html('');
this.collection.each(this.addItem, this);
}
},
});
// Normal Bag View
var bagView = Backbone.View.extend({
tagName: 'div',
id: 'myBag',
template: Handlebars.compile(checkoutBagTemplate),
initialize: function() {
_.bindAll(this);
this.collection.fetch({reset:true});
//this.collection.bind("reset", _.bind(this.render, this));
this.listenTo(this.collection, 'add', this.addItem);
this.listenTo(this.collection, 'reset', this.render);
},
render: function() {
this.$el.html(this.template());
$('#checkoutContainer #details').append(this.el);
this.addAllItems();
return this;
},
addItem: function(item) {
var parent = this;
var itemView = new bagItemView({model: item});
this.$el.find('table tbody').append(itemView.render().el);
},
addAllItems: function() {
if(this.collection.length > 0) {
this.$el.find('table tbody').html('');
this.collection.each(this.addItem, this);
}
}
});
Looking for you help.
Thank you in advance
Cheers,
Vikram

Backbone collection is not rendering

I have problem with render of collection. Its simple model with title and boolean 'completed', when you click on list item it's changing to completed value (true/false). Value is changed ( I know it because when I refresh page, in initialize after fetch() I have collection.pluck, where order made by comparator is correct), but view looks all the time the same.
In collection I have comparator which works like I described upper, after collection.fetch() I have pluck, and pluck gives me well sorted list (but in view I see bad, default order). I dont know how to refresh collection to be well sorted.
Collection is just:
var TodolistCollection = Backbone.Collection.extend({
model: TodoModel,
localStorage: new Store('todos-backbone'),
// Sort todos
comparator: function(todo) {
return todo.get('completed');
}
});
Model is:
var TodoModel = Backbone.Model.extend({
defaults: {
title: '',
completed: false
},
// Toggle completed state of todo item
toggle: function(){
this.save({
completed: !this.get('completed')
});
}
});
return TodoModel;
Single todoView is:
var TodoView = Backbone.View.extend({
tagName: 'li',
template: JST['app/scripts/templates/todoView.ejs'],
events: {
'click .js-complete': 'toggleCompleted'
},
initialize: function(){
this.listenTo(this.model, 'change', this.render);
},
render: function() {
this.$el.html( this.template( this.model.toJSON() ));
this.$el.toggleClass( 'l-completed', this.model.get('completed') );
return this;
},
// Toggle the `"completed"` state of the model.
toggleCompleted: function() {
this.model.toggle();
}
and app View:
var ApplicationView = Backbone.View.extend({
el: $('.container'),
template: JST['app/scripts/templates/application.ejs'],
events: {
'click #add' : 'createOnEnter',
'keypress #addTodo' : 'createOnEnter'
},
// aliasy do DOMu,
// nasluchiwanie w kolekcji czy zaszlo jakies zdarzenie, jesli tak, to wykonuje funkcje
initialize: function() {
this.$input = this.$('.js-input');
this.listenTo(todoList, 'add', this.addOne);
this.listenTo(todoList, 'reset', this.addAll);
this.listenTo(todoList, 'all', this.render);
todoList.fetch();
console.log(todoList.pluck('title'));
},
render: function() {
},
// Generate the attributes for a new Todo item.
newAttributes: function() {
return {
title: this.$input.val().trim(),
completed: false
};
},
// Tworzy nowy model dzieki newAtributes() do localStorage
addTodo: function ( e ) {
e.preventDefault();
if( e.which !== Common.ENTER_KEY || !this.$input.val().trim() ){
return;
}
todoList.create( this.newAttributes() );
this.$input.val('');
},
// Tworzy model i dopisuje go do listy
addOne: function( todo ){
var view = new todoView({ model: todo });
$('.js-todolist').append( view.render().el );
},
// Tworzy nowego todo gdy nacisniemy enter
createOnEnter: function( e ) {
if( e.which !== Common.ENTER_KEY || !this.$input.val().trim() ){
return;
}
todoList.create( this.newAttributes() );
this.$input.val('');
},
// Przy rerenderze, dodaj wszystkie pozycje
addAll: function() {
this.$('.js-todolist').html('');
todoList.each(this.addOne, this);
}
});
return ApplicationView;
When I change listenTo render like that: this.listenTo(todoList, 'all', function(){console.log('whateva')}); I can see that on my click 'all' is triggering (even three times per one click ;s ).
Its hard for me to put it on jsfiddle, but here's git link with all files: https://github.com/ozeczek/ozeczek/tree/master/bb-todo-yo/app/scripts
In app view initialize I've changed todoList.fetch() to todoList.fetch({reset:true});
Second problem was to show in browser right order of todos, I've added to initialize:
this.listenTo(todoList, 'change', this.walcze);
this.listenTo(todoList, 'remove', this.walcze);
and walcze body function is:
walcze: function(){
todoList.sort();
this.$('.js-todolist').html('');
todoList.each(this.addOne, this);
}
Now every time Todo paremeter complete is changed, Im sorting list (comparator by itself isn't), clearing div with list, and rewriting whole list. I think it is not the best way of doing it, but it works.

Backbone collection always with n models

I'm trying make a list with last ten models or less on collection. I have events to add models dynamically with create. This event calls 'add' on collection and with my logic this add correctly one element. But I need add new element, check if collection has more than 10, if it's true remove last model and add new
var model = Backbone.Model.extend({
defaults: function() {
return {id:null}
}
});
var collection = Backbone.Collection.extend({
model:model
});
var view = Backbone.View.extend({
initialize: function(){
var self = this;
this.listenTo(collection, 'add', this.addOne);
this.listenTo(collection, 'reset', this.addAll);
this.listenTo(collection, 'all', this.render);
},
render: function(){
this.$el.html();
return this;
},
addAll: function(){
this.collection.each(this.addOne, this);
},
addOne: function(model){
//this is executed after 'create' but before this I need slice my collection
var view = new view({model:model});
this.$el.prepend(view.render().el);
}
});
thanks
Bind a function in initialize of your Backbone.Collection.extend on the add event in which you're checking the size.
initialize: function() {
this.on('add', function() {
this.checkSize();
});
},
checkSize: function() {
var max = 10;
if (this.length > max) {
this.reset(this.first(max));
}
}
Yoou can do like that:
addOne: function(model){
//this is executed after 'create' but before this I need slice my collection
if !model.get('new_field')
model.set({new_field, ""})
newField = new Date();
while (this.collection.findWhere({new_field:newField})){
newField = new Date();
}
model.set({new_field, newField});
var view = new view({model:model});
this.$el.prepend(view.render().el);
this.checkLength()
},
checkLength: function(){
if (this.collection.length > 10) {
// remove model which gets minimum value by 'new_field'
}
}

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