BackboneJS - Cannot call method 'on' of undefined - backbone.js

I have this simple BackboneJS app and it keeps returning this error on adding new model to collection: Cannot call method 'on' of undefined. Can someone help me. I can't see the problem in here.I have my templates defined in index.html, and I am using Slim framework and NotORM.
(function(){
window.App =
{
Models:{},
Collections: {},
Views : {}
}
window.template = function(id)
{
return _.template( jQuery('#' + id).html());
}
App.Models.Party = Backbone.Model.extend({
});
App.Collections.Partys = Backbone.Collection.extend({
model: App.Models.Party,
url: "http://localhost/BackboneJS/vjezba6/server/index.php/task"
});
App.Views.Party = Backbone.View.extend({
tagName :"div",
className: "box shadow aktivan",
template: template("boxovi"),
initialize: function()
{
this.model.on('change', this.render, this);
},
events:{
"click .izbrisi" : "izbrisi"
},
render: function()
{
var template = this.template( this.model.toJSON() );
this.$el.html(template);
return this;
},
izbrisi: function()
{
this.model.destroy();
},
ukloni: function()
{
this.remove();
}
});
App.Views.Partys = Backbone.View.extend({
tagName:"div",
id: "nosac-boxova",
initialize: function()
{
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(party) {
var partyView = new App.Views.Party({ model: party });
this.$el.append(partyView.render().el);
}
});
App.Views.Dodaj = Backbone.View.extend({
tagName: "div",
template : template("dodajTemp"),
events:
{
'submit' : 'submit'
},
submit : function(e)
{
e.preventDefault();
console.log(e);
var nazivPolje = $(e.currentTarget).find(".naziv").val();
var tekstPolje = $(e.currentTarget).find(".lokal").val();
var noviParty = new App.Views.Party({naziv:nazivPolje, tekst: tekstPolje});
this.collection.create(noviParty);
},
initialize: function()
{
},
render: function()
{
var template = this.template();
this.$el.html(template);
return this;
}
});
/* var kolekcijaPartya = new App.Collections.Partys([
{
naziv:"Ovo je prvi naziv",
tekst: "Ovo je prvi tekst"
},
{
naziv:"Ovo je drugi naziv",
tekst: "Ovo je drugi tekst"
}
]);*/
var kolekcijaPartya = new App.Collections.Partys;
kolekcijaPartya.fetch({
success: function()
{
var partysView = new App.Views.Partys({collection:kolekcijaPartya});
$("#content").prepend(partysView.render().el);
$("div.box").slideAj();
var dodajView = new App.Views.Dodaj({collection: kolekcijaPartya});
$("div#sidebar-right").html(dodajView.render().el);
}
});
})();

var noviParty = new App.Views.Party({naziv:nazivPolje, tekst: tekstPolje});
this.collection.create(noviParty);
so you are trying to add a View to your collection?

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

backbone model and collection

help me understand why the code is not working properly
my code:
var Link = Backbone.Model.extend({
defaults : {
groups: []
}
});
var LinkCollection = Backbone.Collection.extend({
model:Link,
url: 'item.json'
});
var Group = Backbone.Model.extend({
defaults: {
links: []
},
initialize : function() {
this.on("all" , function(event) {
console.log(event);
});
this.on("change:links" , function() {
console.log(this.get("links").length , this.id);
})
},
setLink: function(link) {
var links = this.get("links");
links.push(link);
this.set("links" , links);
this.trigger("change:links" , this);
},
removeLink : function(link) {
var links = this.get("links") , index = links.indexOf(link);
links.splice(index , 1);
this.set("links" , links);
this.trigger("change:links" , this);
}
});
var GroupCollection = Backbone.Collection.extend({
model:Group,
url: 'group.json',
setLinks : function(links) {
var self = this;
this.links = links;
this.links.on('all' , function(event) {
console.log(event);
});
this.links.on('add' , self.setLink , this);
this.links.on('remove' , this.removeLink , this);
this.links.on('reset' , this.resetLinks , this);
},
setLink : function(link) {
var self = this , test = false;
if(self.length ) {
link.get('groups').forEach(function(groupId) {
var group = self.get(groupId);
console.log(group , groupId);
if( group ) {
test = true;
group.setLink(link);
}
});
if(!test) {
self.get("notInGroup").setLink(link);
}
self.get("allObjects").setLink(link);
}
},
resetLinks : function() {
this.links.each(this.setLink);
},
initialize: function() {
var self = this;
this.on('reset' , self.resetLinks , this);
},
removeLink: function(link) {
var self = this;
link.get('groups').forEach(function(groupId) {
var group = self.get(groupId);
if(group) {
group.removeLink(link);
}
})
}
});
var LinkView = Backbone.View.extend({
tagName: 'li',
className : 'list-item',
template: _.template($("#ListView").html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this
}
});
var GroupView = Backbone.View.extend({
tagName : 'li',
className: 'group-list ',
template: _.template($("#GroupView").html()),
initialize: function() {
this.model.on('remove', function() {
console.log('remove');
});
this.model.on('reset' , function() {
console.log('reset');
});
this.model.on('destroy' , function() {
console.log('destroy')
});
},
render: function() {
var model = this.model.toJSON();
this.$el.html(this.template(model));
this.renderLinks(this.model);
this.model.on('change:links' , this.renderLinks.bind(this));
return this;
},
renderLinks : function(group) {
var self = this;
self.$el.find("ul").empty();
group.get("links").forEach(function(link) {
var view = new LinkView({model: link});
self.$el.find("ul").append(view.render().el);
});
}
})
var App = Backbone.View.extend({
el: $('#content'),
initialize: function() {
this.links = new LinkCollection();
this.groups = new GroupCollection();
this.groups.setLinks(this.links);
this.groups.bind('add', this.addGroup, this);
this.groups.bind('reset', this.addGroups, this);
this.links.fetch();
this.groups.fetch({reset: true});
return this;
},
render: function() {
this.$el.html($('#GroupListView').html())
},
addGroup: function(model) {
var view = new GroupView({model: model});
this.$("ul.group-list").append(view.render().el);
},
addGroups: function() {
this.groups.each(this.addGroup)
}
})
$(function() {
var app = new App();
app.render();
})
working example http://plnkr.co/edit/40CCIq0jt2AdmGD61uAn
but instead of the expected list of groups I get incorrect group
this list i tried to get:
All Objects
First
Second
Third
Fourth
Fifth
FirstGroup
First
Third
SecondGroup
Third
Fourth
NotInGroup
Second
Fifth
upd: Groups and Links can come at different times, so the order of their receipt is not critical. the problem is that for some reason the models groups added extra value, which should not be there
Screenshot added. Please look at the highlighted change

backbone.js how to set el to specific div id

My understanding of the Backbone View is that each html element that needs to show model data can be a View by itself.
I wish to create view linked to a specific div to show model data.
My problem is that the code doesn't work if I use anything else than 'body' for el.
Following code does not work:
http://jsfiddle.net/GhaPF/9/
$(document).ready(function() {
var ToDo = Backbone.Model.extend({
defaults: { "date": "today",
"task": ""
},
initialize: function() {}
});
var ToDoList = Backbone.Collection.extend({
model: ToDo
});
var ToDoListView = Backbone.View.extend({
el: "#view1",
initialize: function(myTodoList) {
this.todolist = myTodoList;
this.todolist.bind('add', this.render, this);
},
render: function() {
text = this.todolist.toJSON();
string = JSON.stringify(text);
$(this.el).append(string);
return this;
},
events: {
"keypress #new-todo": "createOnEnter"
},
createOnEnter: function(e) {
if (e.keyCode != 13) return;
if (!$("#new-todo").val()) return;
this.todolist.add({"task": $("#new-todo").val()});
$("#new-todo").val('');
}
});
$("#new-todo").focus();
var todolist = new ToDoList();
var myToDoListView = new ToDoListView(todolist);
});
​
But if I use 'body' for el, it works as I want.
How can I set the el to a specific div ?
solution
http://jsfiddle.net/r3F8q/
you can also use this.setElement('#body1') in render
<div id="view-container">
<input id="new-todo" placeholder="text">
<div id="view1"></div>
<div id="view2"></div>
</div>
​
$(document).ready(function() {
var ToDo = Backbone.Model.extend({
defaults: { "date": "today",
"task": ""
},
initialize: function() {}
});
var ToDoList = Backbone.Collection.extend({
model: ToDo
});
var ToDoListView = Backbone.View.extend({
el: "#view-container",
initialize: function(myTodoList) {
this.todolist = myTodoList;
this.todolist.bind('add', this.render, this);
},
render: function() {
text = this.todolist.toJSON();
string = JSON.stringify(text);
this.$el.find('#view1').append(string);
return this;
},
events: {
"keypress #new-todo": "createOnEnter"
},
createOnEnter: function(e) {
if (e.keyCode != 13) return;
if (!$("#new-todo").val()) return;
this.todolist.add({"task": $("#new-todo").val()});
$("#new-todo").val('');
}
});
$("#new-todo").focus();
var todolist = new ToDoList();
var myToDoListView = new ToDoListView(todolist);
});
​
When you use "#view1"
"keypress #new-todo": "createOnEnter"
is not binded, because #new-todo is not within "#view1". Check API.

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

using localStorage in Backbone.js

I am putting together a backbone example in which models are created edited and deleted. I am able to save new models and edits to local storage, but am having a problem getting localstorage to properly display on refresh. It seems to be loading as a single object, and therefore gives me one model regardless of how many were added.
var Thing = Backbone.Model.extend({
defaults: {
title: 'blank'
}
});
var ThingView = Backbone.View.extend({
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function () {
console.log('deleted');
this.model.destroy();
this.remove();
},
editItem: function () {
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function () {
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({
title: editTitle
});
this.$el.html(this.template(this.model.toJSON()));
},
render: function () {
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name"),
});
var storeVar = localStorage.getItem("store-name");
console.log(storeVar);
var thingsList = new ThingsList;
thingsList.reset(storeVar);
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem',
},
initialize: function () {
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function (e) {
newTitle = $('input').val();
newThing = new Thing({
title: newTitle
});
this.collection.add(newThing);
newThing.save();
console.log(this.collection.length);
},
render: function () {
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function (items) {
var thingView = new ThingView({
model: items
});
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView({
collection: thingsList
});
You need to add the items to your collection, and then to read it in you need to call fetch. You also have a couple of extra trailing commas in your objects.
Here's a slightly modified version of your code which seems to work.
var Thing = Backbone.Model.extend({
defaults:{
title:'blank'
}
});
var ThingView = Backbone.View.extend({
//el: $('body'),
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function(){
console.log('deleted');
this.model.destroy();
//remove view from page
this.remove();
},
editItem: function(){
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function(){
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({title: editTitle});
this.$el.html(this.template(this.model.toJSON()));
},
render: function(){
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var storeVar = localStorage.getItem("store-name");
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name")
});
var things = [
{ title: "Macbook Air", price: 799 },
{ title: "Macbook Pro", price: 999 },
{ title: "The new iPad", price: 399 },
{ title: "Magic Mouse", price: 50 },
{ title: "Cinema Display", price: 799 }
];
console.log(things);
var thingsList = new ThingsList;
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem'
},
initialize: function () {
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function(e){
newTitle = $('input').val();
newThing = new Thing({ title: newTitle });
this.collection.add(newThing);
newThing.save();
//this.model.saveItem;
console.log(this.collection.length);
},
render: function(){
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function(items) {
//console.log('added something');
var thingView = new ThingView({ model: items });
items.save();
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView( {collection: thingsList} );
thingsList.fetch();
console.log(thingsList.toJSON());
thingsList.reset(things);
Edit: I see you are trying to read in the value stored in local storage under "store-name", the way backbone-localStorage works is that it uses the name of the store (in your case "Store-name") to store the ids of the rest of the models and then saves each model under a combination of the store name and the id, so say you had three models, you would end up with 4 entries in local storage,
localStorage["store-name"] //id1, id2, id3"
localStorage["store-name-id1"] //model with id1
localStorage["store-name-id2"] // model with id2
localStorage["store-name-id3"] // model with id3
EDIT 2
Here's a link to a jsfiddle of your code, to start I'm leaving the line thingsList.fetch(); commented out, uncomment that line and comment out thingsList.add(things); and run it a second time and it should pull the models from local Storage (I left an alert in there).
var Thing = Backbone.Model.extend({
defaults: {
title: 'blank'
}
});
var ThingView = Backbone.View.extend({
template: _.template('<b><button id="remove">X</button> <b><button id="edit">Edit</button> <%= title %></b>'),
editTemplate: _.template('<input class="name" value="<%= name %>" /><button id="save">Save</button>'),
events: {
"click #remove": "deleteItem",
"click #edit": "editItem",
"click #save": "saveItem",
},
deleteItem: function () {
console.log('deleted');
this.model.destroy();
this.remove();
},
editItem: function () {
console.log('editing');
this.$el.html(this.editTemplate(this.model.toJSON()));
},
saveItem: function () {
console.log('saved');
editTitle = $('input.name').val();
console.log(editTitle);
this.model.save({
title: editTitle
});
this.$el.html(this.template(this.model.toJSON()));
},
render: function () {
var attributes = this.model.toJSON();
this.$el.append(this.template(attributes));
return this;
}
});
var ThingsList = Backbone.Collection.extend({
model: Thing,
localStorage: new Store("store-name"),
});
var storeVar = localStorage["store-name-7ee7d1e3-bbb7-b3e4-1fe8-124f76c2b64d"];
console.log(storeVar);
var thingsList = new ThingsList;
//thingsList.reset(storeVar);
var ThingsListView = Backbone.View.extend({
el: $('body'),
events: {
'click #add': 'insertItem',
},
initialize: function () {
thingsList.fetch();
thingsList.toJSON();
this.render();
this.collection.on("add", this.renderThing, this);
},
insertItem: function (e) {
newTitle = $('input').val();
newThing = new Thing({
title: newTitle
});
this.collection.add(newThing);
newThing.save();
console.log(this.collection.length);
},
render: function () {
_.each(this.collection.models, function (items) {
this.renderThing(items);
}, this);
},
renderThing: function (items) {
var thingView = new ThingView({
model: items
});
this.$el.append(thingView.render().el);
}
});
var thingsListView = new ThingsListView({
collection: thingsList
});

Resources