Backbone.js auto render view after destroy - backbone.js

I have the 2 views for my model. One creates the ul view, the other add the li elements. The views work but they do not update after the destroy function is called. I've tried various ways to bind to the render function, but it is not being called after this.model.destroy(); What am I missing?
var NoteListView = Backbone.View.extend({
tagName:'ul',
className: 'note-group',
initialize:function () {
_.bindAll(this, "render"); // I've tried all these:
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.render, this);
this.model.bind("reset", this.render, this);
this.model.bind('change', _.bind(this.render, this));
this.model.on('change',this.render,this);
},
render:function (eventName) {
_.each(this.model.models, function (note) {
$(this.el).append(new NoteListItemView({
model:note
}).render().el);
}, this);
return this;
}
});
var NoteListItemView = Backbone.View.extend({
tagName:"li",
className: 'note-item',
template:_.template($('#tpl-note-item').html()),
initizlize:function(){
this.model.on('change',this.render,this);
},
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .note-btn-delete": "deleteNote"
},
deleteNote: function(e){
this.model.destroy();
}
});
Edit: here's the Collection:
var NoteCollection = Backbone.Collection.extend({
model:Note,
url:"api.php/notes",
initialize: function (models){
this.filtered = new Backbone.Collection(models);
},
filterByCid: function(id) {
var filtered = _.filter(this.models, function (model) {
if (id==0) return true;
return model.get("contactid") == id;
});
}
});
In the router:
this.noteList = new NoteCollection();
this.noteList.fetch({data:{contactid:id},
success: function (collection){
if (collection.length>0){
$('#note-container').html(new NoteListView({model:collection}).render().el);
}
else {
$('#note-container').html('No notes');
}
}
})

I see 2 issues with enter code here your code.
First
You are not listening to the remove event on the NodeListItemView
Second
_.each(this.model.models,
supposed to be
_.each(this.collection.models,
If models is present on the model, then it is supposed to be in the options of the model
_.each(this.model.options.models,
Code
var NoteListView = Backbone.View.extend({
tagName: 'ul',
className: 'note-group',
initialize: function () {
this.listenTo.on(this.collection, 'reset', this.render);
},
render: function (eventName) {
_.each(this.collection.models, function (note) {
$(this.el).append(new NoteListItemView({
model: note
}).render().el);
}, this);
return this;
}
});
var NoteListItemView = Backbone.View.extend({
tagName: "li",
className: 'note-item',
template: _.template($('#tpl-note-item').html()),
initizlize: function () {
_.bindAll(this, "deleteNote", "removeView");
this.listenTo.on(this.model, 'change', this.render);
this.listenTo.on(this.model, 'remove', this.removeView);
},
render: function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .note-btn-delete": "deleteNote"
},
deleteNote: function (e) {
this.model.destroy();
},
removeView : function() {
this.remove();
}
});
EDIT
Change this line
new NoteListView({model:collection}).render().el
to use collection instead
new NoteListView({collection :collection}).render().el
And I do not believe your fetch method takes in any data
this.noteList.fetch({data:{contactid:id})
this.noteList.fetch()
It only takes in success and error callbacks

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 not initializing view. "undefined is not a function"

I'm having problem in backbone where it's not finding my view. Here's the code
Here's the views.
App.Views.SummaryTableView = Backbone.View.extend({
tagName: 'tbody',
initialize: function () {
this.childViews = [];
this.collection.on('add', this.addOne, this);
this.collection.on('change reset', this.render, this);
console.log(this.collection);
},
addOne: function (appSummary) {
console.log('should be receiving model');
console.log(appSummary);
var appSumTable = new App.Views.SummaryListView({ model: appSummary });
console.log(appSummary);
this.$el.append(appSumTable.render().el);
this.childViews.push(appSumTable);
},
render: function () {
console.log('rendiring collection: ' + this.collection);
this.collection.each(this.addOne, this);
console.log('sending the model');
return this;
},
close: function () {
this.remove();
this.unbind();
this.childViews = [];
}
});
App.Views.SummaryListView = Backbone.View.extend({
tagName: 'tr',
template: template('app-summary-table-template'),
initialize: function () {
console.log(this.model);
this.model.on('add', this.render, this);
},
render: function () {
console.log('rendering');
var mod = this.model.toJSON();
this.$el.html(this.template(mod));
return this;
},
close: function () {
this.remove();
this.unbind();
}
});
The SummaryTableView has the collection, and the view sends the model to the SummaryListView. The collection is working fine, and the model contains the data. But for some reasons, when I run the code, it keeps saying SummaryListView is undefined. It can't find the view. Am I doing something wrong? I get the error in this line :
var appSumTable = new App.Views.SummaryListView({ model: appSummary });
you are referring SummaryListView before it is declared , hence the error So
you must Declare SummaryListView before SummaryTableView
the order should be like this
App.Views.SummaryListView = Backbone.View.extend({
tagName: 'tr',
.
.
});
App.Views.SummaryTableView = Backbone.View.extend({
tagName: 'tbody',
.
.
addOne: function (appSummary) {
console.log('should be receiving model');
console.log(appSummary);
var appSumTable = new App.Views.SummaryListView({ model: appSummary});
.
.
});
Fiddle

Pass model in a view backbone

Hi I've this function in my router:
home: function() {
var attore= new Attore();
var page = new ListPostView({
model: this.attore
});
this.changePage(page);
},
and the model Attore is:
var Attore = Backbone.Model.extend({
defaults: {
"nome": "caesar salad",
"cognome": "ravioli"
},
});
return Attore;
});
but happens an error that tell me model is undefined in the view:
var ListPostView = Backbone.View.extend({
tagName: "ul",
id: "list",
template: Handlebars.compile(template),
initialize: function () {
console.log(attore);
this.model.bind("reset", this.render, this);
},
render: function (eventName) {
$(this.el).empty();
_.each(this.model.models, function (ad) {
$(this.el).append(new SinglePostView({
model: ad
}).render().el);
}, this);
return this;
}
});
return ListPostView;
});

View doesn't shows backbone models in template

i'm starting with Backbone and Laravel, and i have severals questions because i don`t find anything in Spanish (maybe i don't know how to search, therefore it's easier to ask).
Here are my Models:
window.mMateria = Backbone.Model.extend({
defaults: {
nombremateria: ""
},
});
window.cMaterias = Backbone.Collection.extend({
url: "materias",
model: mMateria,
initialize: function() {
this.fetch();
}
});
Here are my Views:
window.vMaterias = Backbone.View.extend({
tagName: 'ul',
model: cMaterias,
className:'list-materias',
initialize: function () {
_.bindAll(this, "render");
},
render: function(){
$(this.el).append("Renderizando!"); //It appears
_.each(this.model.models, function (aMater) {
console.log(aMater); //HERE IT DOESN'T ENTER, doesn't show anything
$(this.el).append(new vMateria({model:aMater}).render().el);
}, this);
return this;
},
el: $(".container-fluid")
});
window.vMateria = Backbone.View.extend({
initialize:function () {
_.bindAll(this, "render");
this.model.bind("change", this.render(), this);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
},
className: "item-materia",
el: $(".container-fluid"),
template: _.template($('#pl_materia').val()),
});
Then initialize:
cmaterias = new cMaterias();
console.log(cmaterias); //it returns 41 signatures
vmaterias = new vMaterias({model: cmaterias});
console.log(vmaterias); //Shows child {cid: "view1", model: child, ...
vmaterias.render().el;
Please help me and excuse my English, I dont know if Laravel with the
return Response::eloquent(Materia::all()); is the problem. Be as especific as possible. Dios los bendiga.
Try these changes.
window.vMaterias = Backbone.View.extend({
tagName: 'ul',
className:'list-materias',
render: function(){
this.$el.empty();
this.$el.append("Renderizando!"); //It appears
this.collection.each(function (aMater) {
console.log(aMater); //HERE IT DOESN'T ENTER, doesn't show anything
this.$el.append(new vMateria({model:aMater}).render().el);
}, this);
return this;
},
el: $(".container-fluid")
});
cmaterias = new cMaterias();
console.log(cmaterias); //it returns 41 signatures
vmaterias = new vMaterias({collection: cmaterias});

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