backbone.js events not firing after re-render even with delegateEvents() - backbone.js

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

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

Cannot read property 'toJSON' of undefined + backbone

I am really new to backbone, and in a world of confusion behind it. Right now I am trying to render a view of a single person, preferably without iterating through the entire collection, using the collection.get() method.
Here is the user collection:
App.Collections.UserCollection = Backbone.Collection.extend({
url: "/users",
model: App.Models.User,
initialize: function(){
console.log('users collection');
},
});
The user model:
App.Models.User = Backbone.Model.extend({
rootURL: '/users',
initialize: function(){
console.log('User model being generated');
}
});
the Users View (all users):
App.Views.Users = Backbone.View.extend({
el: "body",
model: 'user',
initialize: function(){
console.log('User view rendering');
this.collection.fetch();
this.listenTo(this.collection, 'reset', this.addAll);
},
clearDiv: function(){
var container = $('#render-area');
$('body').removeClass('homepage');
$('#main-nav').fadeIn(100);
container.empty();
this.addUser();
},
addUser: function(){
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
$('#render-area').append(user.el);
},
events: {
'click #view-profile' : 'clearDiv'
}
});
and the viewUser view (single user):
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.render();
},
render: function(){
$('#render-area').html(this.template(this.model.toJSON()));
},
});
I thought that I was grabbing the single user model in the Users view, and passing it to the ViewUser view with these lines
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
But it seems that I am not. Any help and/or explanation is appreciated, thanks!
did you check the this.collection.get is return an object?
Maybe it can not fetch the currentUser?
before render the view you should
a) check the model is a valid model before calling the view and render a "not found" view or
b) in the view check if the model is valid and render a "not found" view
c) or you can handle it in handlebars template
Example A (untested):
var current_user_model = this.collection.get(currentUser);
if (current_user_model) {
var user = new App.Views.ViewUser({ model: current_user_model });
} else {
new AppViews.ViewUserNotFound();
}
Example B :
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.templateNotFound = HandlebarsTemplates['user_profile_not_found'];
this.render();
},
render: function(){
if(this.model) {
$('#render-area').html(this.template(this.model.toJSON()));
} else {
$('#render-area').html(this.templateNotFound());
}
},
});
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
Example C:
App.Views.ViewUser = Backbone.View.extend({
initialize: function(){
console.log("single user view");
this.template = HandlebarsTemplates['user_profile'];
this.templateNotFound = HandlebarsTemplates['user_profile_not_found'];
this.render();
},
render: function(){
if(!this.model) {
this.model = new this.model(); // create model with defaults
}
$('#render-area').html(this.template(this.model.toJSON()));
},
});
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
all examples untested..
hope this helps a little bit,
best Carsten
I would do that:
App.Views.Users = Backbone.View.extend({
el: "body",
model: 'user',
initialize: function(){
console.log('User view rendering');
this.listenTo(this.collection, 'reset', this.addAll);
},
clearDiv: function(){
var self = this;
this.collection.fetch({
success: function() {
var container = $('#render-area');
$('body').removeClass('homepage');
$('#main-nav').fadeIn(100);
container.empty();
self.addUser();
}
});
},
addUser: function(){
var current_user_model = this.collection.get(currentUser);
var user = new App.Views.ViewUser({ model: current_user_model });
$('#render-area').append(user.el);
},
events: {
'click #view-profile' : 'clearDiv'
}
});

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

Backbone appends to list view instead of replacing it

I am producing a single page website with Wordpress and Backbone.js, i have come into a problem with when i fetch new data. It simply adds DOM elements onto the container el rather than replacing them. The collection updates correctly as i can see it has the right amount of elements in the console.
var PostItem = Backbone.Model.extend();
var PostItems = Backbone.Collection.extend({
model: PostItem,
url: '/wp-admin/admin-ajax.php'
});
var postItems = new PostItems();
var PostView = Backbone.View.extend({ /* Model View */
tagName : 'article',
className : 'widget',
template : _.template( $('#widgetPost').html() ),
render: function(){
var attributes = this.model.toJSON();
this.$el.html( this.template( attributes ) );
return this;
}
});
var PostListView = Backbone.View.extend({ /* Collection View */
el : '#content',
initialize: function(){
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.addAll, this);
},
addOne: function(postItem){
var postView = new PostView({ model : postItem });
this.$el.append( postView.render().el );
},
addAll: function(){
this.collection.forEach(this.addOne, this);
},
render: function(){
this.addAll();
},
});
var postListView = new PostListView({
collection : postItems
});
$(function(){
$('a#posts').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_posts'
}
});
return false;
});
$('a#pages').click(function(){
postItems.fetch({
data: {
action: 'do_ajax',
fn: 'get_the_pages'
}
});
return false;
});
});
You need to clear out your collectionView's $el! :)
addAll: function(){
this.$el.empty();
this.collection.forEach(this.addOne, this);
}
This should do the trick.

How to call view from the view in backbone.js

I have two view files views.js and views2.js .On button click views.js call.On views.js there is a button that needs to triggers views2.js.But the problem is that on button click views2.js
is not working.
I have written some code.
In views.js
define( ["backbone"], function(Backbone) {
var UserView = Backbone.View.extend({
tagName: "div",
render: function(){
this.$el.html(this.model.get("desc") + "<button id='btn2' class='btn btn- primary'>Next Page</button>");
return this;
}
});
var NextPageView = Backbone.View.extend({
events: {
'click': 'firstquestion'
},
render: function(){
return this;
},
firstquestion: function(){
//to solve circular dependency
require(['routes'],function(routes){
var router = routes.pageRouter;
router.navigate('view', {trigger: true});
});
}
});
var views2 = require("views2");//should I call views2 here
var np2 = new views2.NextPageView2({el: $("#btn2")});
return {
UserView: UserView,
NextPageView: NextPageView
};
});
In views2.js
define( ["backbone"], function(Backbone) {
var UserView2 = Backbone.View.extend({
tagName: "div",
render: function(){
this.$el.html(this.model.get("desc") + "<button id='btn3' class='btn btn-primary'>Next Page</button>");
return this;
}
});
var NextPageView2 = Backbone.View.extend({
events: {
'click': 'secondquestion'
},
render: function(){
return this;
},
secondquestion: function(){
require(['routes2'],function(routes2){
var router = routes2.pageRouter2;
router.navigate('view', {trigger: true});
});
}
});
return {
UserView2: UserView2,
NextPageView2: NextPageView2
};
});
You need to put a Require.js dependency on the calling view.

Resources