Backboe memory leak in subviews - backbone.js

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

Related

backbone fetch collection on infinite scroll

I'm learning backbone and now thinking of how to apply an infinite scroll and fetch/load lets say 20 items from my collection every time the scroll is at the bottom of the page.
I have been searching around a lot after different libs and such without really getting any closer. Anyone that can explain/show how this is best done?
I have now added the infiniscroll.js plugin and trying to get it to work. But on scroll it won't load new items. What am i supposed to do on the appendRender? and how?
var StartView = Backbone.View.extend({
tagName: "section",
id: "start",
className: "content",
events: {
},
initialize: function(){
$(".container").html(this.el);
console.log("init start");
this.template = _.template($("#start_template").html(), {} );
this.collection = new VideoCollection();
_.bindAll(this, "render");
this.render();
this.infiniScroll = new Backbone.InfiniScroll(this.collection, {
success: this.appendRender,
pageSize: this.collection.length,
scrollOffset: 100
});
},
appendRender: function() {
var self = this;
self.$el.html(self.template);
self.$el.find(".videos").append("<div style='margin-bottom:30px; width:100%; height:170px; float:left; background-color:#e4e4e4;'>fff</div>")
},
render: function(){
var self = this;
this.$el.html("loading");
console.log("render start")
},
kill: function() {
console.log("kill start");
this.remove();
}
});
return StartView;
The backbone-pageable plugin supports infinite scrolling.
It's just a matter of your collection extending Backbone.PageableCollection, and you specifying some extra properties. There's also an example of a backbone view listening to the changing collection, as well as fetching on scroll.
It's all described on the github page. It's updated fairly often.
I would have done it something like this (although the document.addEventListener('scroll')-part isn't really elegant
(function() {
"use strict";
var Item = Backbone.Model.extend({});
var Items = Backbone.Collection.extend({
model: Item,
url: "/api/items/"
});
var ItemView = Backbone.View.extend({
tagName: "li",
render: function() {
this.$el.html(this.model.get("name"));
return this;
}
});
var ItemsList = Backbone.View.extend({
tagName: "ul",
offset: 0,
limit: 60,
initialize: function() {
this.collection = new Items();
this.collection.on("reset", this.addAll, this);
this.collection.on("add", this.addOne, this);
this.getItems();
},
render: function () {
return this;
},
getItems: function () {
this.collection.fetch({
"data": {"offset": this.offset, "limit": this.limit},
"success": _.bind(function(e){
this.offset += this.limit;
}, this)
});
},
addOne: function(item) {
var view = new ItemView({model: item});
this.$el.append(view.render().$el);
},
addAll: function() {
this.collection.each(this.addOne, this);
}
});
var itemsList = new ItemsList();
$(document.body).append(itemsList.render().$el);
document.addEventListener('scroll', function (event) {
if (document.body.scrollHeight == document.body.scrollTop + window.innerHeight) {
itemsList.getItems();
}
});
}());

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 view doesn't render template passed to it

I need to be able to pass different template IDs to different routes.
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
var vent = _.extend({}, Backbone.Events);
_.templateSettings.interpolate = /\[\[(.+?)\]\]/g;
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'send-message' : 'sendMessage',
'*other' : 'other'
},
index: function() {
t = new (App.Collections.Tables.extend({ url: 'main-contact'}))();
tables = App.Views.Tables({ collection: t, template: 'mainContactTemplate' });
$('#web-leads').html(tables.el);
},
sendMessage: function() {
// t = new (App.Collections.Tables.extend({ url: 'send-message'}))();
// tables = new App.Views.Tables.extend({ collection: t, template: template('sendMessageTemplate')});
// $('#web-leads').html(tables.el);
},
other: function() {
}
});
// Main Contact
App.Models.Table = Backbone.Model.extend({});
App.Collections.Tables = Backbone.Collection.extend({
model: App.Models.Table,
initialize: function(models, options) {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Tables = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.collection.on('reset', this.render, this);
},
render: function() {
return this.collection.each(this.addOne, this);
},
addOne: function(model) {
var t = new App.Views.Table({ model: model});
this.$el.append(t.render().el);
return this;
}
});
App.Views.Table = Backbone.View.extend({
tagName: 'li',
initialize: function(options) {
this.template = options.template;
console.log(this.options);
},
retrieveTemplate: function(model) {
return _.template($('#' + this.template).html(), model);
},
render: function() {
this.$el.html(this.retrieveTemplate(this.model.toJSON()));
return this;
}
});
new App.Router();
Backbone.history.start();
})();
But I get an error than n is undefined. I think I need to pass this.template into my retrieveTemplate function. But shouldn't it already be set? This code works, by the way, if I hard code in the name of the template ID in the retrieveTemplate function.
EDIT: the template isn't being passed from the call in the router. That's where this is breaking down.
EDIT: I took out the call to extend in the second line of the index route and now I get this._configure is not a function
WORKING VERSION:
(function() {
window.App = {
Models: {},
Collections: {},
Views: {},
Router: {}
};
var vent = _.extend({}, Backbone.Events);
_.templateSettings.interpolate = /\[\[(.+?)\]\]/g;
App.Router = Backbone.Router.extend({
routes: {
'' : 'index',
'send-message' : 'sendMessage',
'*other' : 'other'
},
index: function() {
var t = new (App.Collections.Tables.extend({ url: 'main-contact'}))();
var tables = new (App.Views.Tables.extend({ collection: t, options: {template: 'mainContactTemplate' }}))();
$('#web-leads').html(tables.render().el);
},
sendMessage: function() {
// t = new (App.Collections.Tables.extend({ url: 'send-message'}))();
// tables = new App.Views.Tables.extend({ collection: t, template: template('sendMessageTemplate')});
// $('#web-leads').html(tables.el);
},
other: function() {
}
});
// Main Contact
App.Models.Table = Backbone.Model.extend({});
App.Collections.Tables = Backbone.Collection.extend({
model: App.Models.Table,
initialize: function(models, options) {
this.fetch({
success: function(data) {
//console.log(data.models);
}
});
if (options) {
this.url = this.url || options.url;
}
}
});
App.Views.Tables = Backbone.View.extend({
tagName: 'ul',
initialize: function(options) {
this.collection.on('reset', this.render, this);
this.template = this.options.template;
},
render: function() {
this.collection.each(this.addOne, this);
return this;
},
addOne: function(model, options) {
//console.log(model);
var t = new App.Views.Table({ model: model, template: this.options.template});
this.$el.append(t.render().el);
return this;
}
});
App.Views.Table = Backbone.View.extend({
tagName: 'li',
initialize: function(options) {
//console.log(this.options);
this.template = this.options.template;
},
retrieveTemplate: function(model) {
return _.template($('#' + this.template).html(), model);
},
render: function() {
//console.log(this);
this.$el.html(this.retrieveTemplate(this.model.toJSON()));
return this;
}
});
new App.Router();
Backbone.history.start();
})();
Your router says this:
tables = App.Views.Tables({ collection: t, template: 'mainContactTemplate' });
So you're giving a template: '...' to App.Views.Tables. The initialize in App.Views.Tables looks like this:
initialize: function() {
this.collection.on('reset', this.render, this);
}
so it ignores the template option. If we look at App.Views.Table (singular!), we see this:
initialize: function(options) {
this.template = options.template;
console.log(this.options);
}
but App.Views.Table is instantiated without a template option:
var t = new App.Views.Table({ model: model});
You need to fix how you use App.Views.Table. Backbone will put a view's constructor options in this.options for you so you just need to say:
var t = new App.Views.Table({ model: model, template: this.options.template });
A couple other things to consider:
You have some accidental globals in your router's index method, you should have var t and var tables rather than just t and tables.
A view's render method conventionally returns this so that you can say $x.append(v.render().el) so you might want to adjust your render methods to match the convention.
You probably need to bind the context. Underscore can help you with that.
.bindAll or .bind should do it.
I typically just use _.bindAll during initialization as shown below.
...
initialize: function(options) {
_.bindAll(this); // apply appropriate context
this.template = options.template;
},
...
Hope this helped, best of luck.

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

Using Event Aggregator to load a view with different model in backbone js

I am new to backbone.js started with backbone a week ago. I had to make a demo. The main idea behind it is when the page is loaded, I need to show the courses,and by default the students list for the first course in the list. Here is the code to display the course list which is in course.js file
//Model
var Course = Backbone.Model.extend({
urlRoot: '/api/courses/',
idAttribute: 'Id',
defaults:{
Id: null,
Name: ""
},
validate: function (attr) {
if (!attr.Name)
return "Name is required";
}
});
var Courses = Backbone.Collection.extend({
model: Course,
url: '/api/courses'
});
//Views
var CourseList = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection.on('reset', this.renderAll, this);
this.collection.on('add', this.render, this);
this.collection.fetch();
_.bindAll(this, 'renderAll', 'render');
return this;
},
renderAll: function () {
this.collection.each(this.render);
$('#spnStdntCourseName').text('Students Enrolled in ' + this.collection.at(0).get("Name"));
},
render: function (model) {
var item = new CourseItem({ model: model });
this.$el.append(item.el);
},
events: {
"click #btnAddCourse": "createNewCourse",
"keypress #txtNewCourse": "createOnEnter"
},
createOnEnter: function (e) {
if (e.keyCode == 13) {
this.createNewCourse();
}
},
createNewCourse: function () {
this.collection.create({ Name: this.$el.find('#txtNewCourse').val() });
this.$el.find('#txtNewCourse').val('');
}
});
var CourseItem = Backbone.View.extend({
tagName: 'li',
className: 'courseli',
events: {
'click .remove': 'deleteItem',
'click .edit': 'showEdit',
'click': 'courseClicked'
},
initialize: function () {
this.template = _.template($('#course').html()),
this.model.on('change', this.render, this);
this.render();
},
render: function () {
var html = this.template(this.model.toJSON());
this.$el.html('').html(html);
},
courseClicked: function () {
$('#spnStdntCourseName').text('Students Enrolled in ' + this.model.get("Name"));
Vent.trigger('studentDetails',"how to load student list from here based on courseID...?");
},
showEdit: function (event) {
event.preventDefault();
Vent.trigger('edit', this.model);
},
deleteItem: function () {
this.model.destroy();
this.remove();
}
});
var CourseEdit = Backbone.View.extend({
el: '#courseEdit',
events: {
'click #save': 'save',
'click #cancel': 'cancel'
},
initialize: function () {
_.bindAll(this, 'render', 'save');
Vent.on('edit', this.render);
this.template = _.template($('#courseEditTemplate').html())
},
render: function (model) {
var data, html;
this.model = model;
data = this.model.toJSON();
html = this.template(data);
this.$el.html(html)
.show()
.find('#name')
.focus();
this.model.on('error', this.showErrors, this);
},
save: function (event) {
var self = this;
this.model.save({
'Name': this.$el.find('#name').val()
}, {
success: function () {
alert('Saved!');
if (!window.courses.any(function (course) {
return course.get('Id') === self.model.get('Id');
})) {
window.courses.add(self.model);
}
self.$el.hide();
}
});
},
cancel: function () {
this.$el.hide();
},
showErrors: function (model, error) {
var errors = '';
if (typeof error === 'object') {
errors = JSON.parse(error.responseText).join('<br/>');
alert(errors);
}
else {
alert(error);
}
}
});
var Vent = _.extend({ }, Backbone.Events);
window.courses = new Courses();
$(function () {
var edit = new CourseEdit(),
list = new CourseList({
collection: window.courses,
el: '#coursesList'
});
});
Please take a look at the 'courseClicked' function inside CourseItem View, it is supposed to load the students list when a course item is clicked.
Now I have my Student model and views in students.js as below
var Student = Backbone.Model.extend({
urlRoot: '/api/students/',
idAttribute: 'Id',
defaults: {
Id: null
},
validate: function (attr) {
if (!attr.Name)
return "Name is required";
}
});
var Students = Backbone.Collection.extend({
model: Student,
url: '/api/students'
});
//Views
var StudentList = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection.on('reset', this.renderAll, this);
this.collection.on('add', this.render, this);
this.collection.fetch({ data: $.param({ courseId: 11 }) });
_.bindAll(this, 'renderAll', 'render');
return this;
Vent.on('studentDetails', this.render);
},
renderAll: function () {
this.collection.each(this.render);
},
render: function (model) {
var item = new StudentItem({ model: model });
this.$el.append(item.el);
},
events: {
"click #btnAddStudent": "createNewStudent",
"keypress #txtNewStudent": "createOnEnter"
},
createOnEnter: function (e) {
if (e.keyCode == 13) {
this.createNewStudent();
}
},
createNewStudent: function () {
this.collection.create({ Name: this.$el.find('#txtNewStudent').val() });
this.$el.find('#txtNewStudent').val('');
}
});
var StudentItem = Backbone.View.extend({
tagName: 'li',
className: 'studentli',
events: {
'click .remove': 'deleteItem',
'click': 'studentClicked'
},
initialize: function () {
this.template = _.template($('#student').html()),
this.model.on('change', this.render, this);
this.render();
},
render: function () {
var html = this.template(this.model.toJSON());
this.$el.html('').html(html);
},
studentClicked: function () {
var Id = this.model.get("Id");
},
deleteItem: function () {
this.model.destroy();
this.remove();
}
});
window.students = new Students();
$(function () {
var studentDetails = new StudentList({
collection: window.students,
el: '#studentsList'
});
});
so inside document.ready I have studentDetails variable which loads the students list.Here is my problem as of now I have loaded the students list on page load by passing some hard code parameter inside fetch like below
this.collection.fetch({ data: $.param({ courseId: 11 }) });
but what I need to show is, the student list for the first course in the courselist view when the page is loaded, and in later stages, the student list for each and every course item clicked.For that purpose if you can remember in the "courseClicked" function inside "CourseItem" view in course.js, I have used
Vent.trigger('studentDetails',"how to load student list from here based on courseID...?");
studentDetails is the var that I have initialised in students.js(in the code above) like this
window.students = new Students();
$(function () {
var studentDetails = new StudentList({
collection: window.students,
el: '#studentsList'
});
});
So when I trigger the studentDetails I defnitely be needing the student model inside my courseClicked function,which is not available in that context. I beleive you guys understood my problem from the above explanation. So how do I fix this...? Is the approach I followed wrong..? Any good alternative,need suggestions. Hope there is not too much noise in the question.
EDIT
var CourseList = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.students = new Students();
var studentList = new StudentList({
collection: this.students,
el: '#studentsList'
});
this.collection.on('reset', this.renderAll, this);
this.collection.on('add', this.render, this);
this.collection.fetch();
_.bindAll(this, 'renderAll', 'render');
return this;
},
renderAll: function () {
this.collection.each(this.render);
$('#spnStdntCourseName').text('Students Enrolled in ' + this.collection.at(0).get("Name"));
this.students.fetch({ data: $.param({ courseId: this.collection.at(0).get("Id") }) });
},
render: function (model) {
this.$el.html("");
var item = new CourseItem({ model: model, students: this.students});
this.$el.append(item.el);
}
})
I have made following changes
1.students in collection to this.students(inside initialize of "CourseList" view) in the below code
initialize: function () {
this.students = new Students();
var studentList = new StudentList({
collection: this.students,
el: '#studentsList'
});
2.I have fetched the students inside renderAll function instead of render function because for every course item that is fetched the student is also fetched.I mean if there are 6 courses i get to see the students for course 0 in the collection 6 times
renderAll: function () {
this.collection.each(this.render);
$('#spnStdntCourseName').text('Students Enrolled in ' + this.collection.at(0).get("Name"));
this.students.fetch({ data: $.param({ courseId: this.collection.at(0).get("Id") }) });
SubQuestion
In the "CourseList" we have initialize function as below
initialize: function () {
this.students = new Students();
var studentList = new StudentList({
collection: this.students,
el: '#studentsList'
});
The studentsList el is as below
<div id="studentsList" class="box">
<div class="box-head">
<h2 class="left">
<span id="spnStdntCourseName"></span>
</h2>
</div>
<div>
<input type="text" id="txtNewStudent" placeholder="Add New Student" />
<button id = "btnAddStudent">+ Add</button>
</div>
</div>
whenever I do this.$el.html("") inside render function of StudentList view like below
var StudentList = Backbone.View.extend({
tagName: 'ul',
render: function (model) {
this.$el.html("");
var item = new StudentItem({ model: model });
this.$el.append(item.el);
},
......
I loose the button and textbox elements inside the studentsList el, and the ul is not shown when I view source code in my browser which I mentioned as tagName, but I do see li which is tagName for studentItem view.Can you say what I am doing wrong
Thanks for our patience
First, you want to let the CourseList view to keep track of a Students collection as well as a StudentList. The Students collection will be passed into each CourseItem view to fetch. After it renders all CourseItem, it will tell the Students collection to fetch the first course's students.
var CourseList = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.students = new Students();
var studentList = new StudentList({
collection: students,
el: '#studentsList'
});
this.collection.on('reset', this.renderAll, this);
this.collection.on('add', this.render, this);
this.collection.fetch();
_.bindAll(this, 'renderAll', 'render');
return this;
},
render: function (model) {
this.$el.html("");
var item = new CourseItem({ model: model, students: this.students});
this.$el.append(item.el);
this.students.fetch({ data: $.param({ courseId: 0 }) }); // fetch the first
},
...
})
The CourseItem will store the Students collection, and on being clicked, fetch the correct students using its model's id.
var CourseItem = Backbone.View.extend({
...
initialize: function() {
this.students = this.options.students;
},
...
courseClicked: function () {
$('#spnStdntCourseName').text('Students Enrolled in ' + this.model.get("Name"));
var courseId = this.model.id;
this.students.fetch({ data: $.param({ courseId: courseId }) });
},
...
})
In StudentList view, you don't let it fetch by itself.
var StudentList = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection.on('reset', this.renderAll, this);
this.collection.on('add', this.render, this);
_.bindAll(this, 'renderAll', 'render');
return this;
},
...
render: function() {
this.$el.html(""); // Reset the view for new students
var item = new StudentItem({ model: model });
this.$el.append(item.el);
}
})
Then in your main script:
window.courses = new Courses();
$(function () {
var courseList = new CourseList({
collection: window.course,
el: '#courseList'
});
});
DISCLAIMER: Untested codes.

Resources