I have 2 pages for my Backbone app. Page 1 uses the fetch() method on a collection to get data.
When I navigate to Page 2, and then back to Page 1, I can see the fetch() data getting logged twice. The fetch calls increment upwards with each new time that I navigate away from, and then back to Page One.
Is this a zombie view? How do I stop this from happening?
Here is my JavaScript using Backbone:
var MyModel = Backbone.Model.extend({
defaults: {
'id': 'null',
'color': '',
'date': '',
'name': ''
}
});
var MyCollection = Backbone.Collection.extend({
model: MyModel,
url: 'https://api.mongolab.com/api/1/databases/testdatabase/collections/Content?apiKey=xxxxxxxxxxxxxxx'
});
var aCollection = new MyCollection();
var MyViewOne = Backbone.View.extend({
el: '.js-container',
initialize: function () {
this.collection = aCollection;
this.listenTo(this.collection, 'sync', this.render);
this.collection.fetch();
},
template: _.template( $('#One').html() ),
render: function () {
console.log('render One');
console.log(this.collection);
this.$el.html( this.template({collection: this.collection}) );
return this;
}
});
var MyViewTwo = Backbone.View.extend({
el: '.js-container',
template: _.template( $('#Two').html() ),
render: function () {
console.log('render Two');
this.$el.html( this.template() );
return this;
}
});
var MyRouter = Backbone.Router.extend({
routes: {
'': 'pageOne',
'pageone': 'pageOne',
'pagetwo': 'pageTwo'
},
pageOne: function () {
var myViewOne = new MyViewOne();
},
pageTwo: function () {
var myViewTwo = new MyViewTwo();
myViewTwo.render();
}
});
var myRouter = new MyRouter();
Backbone.history.start();
Here is my HTML:
<div>
<ul class="nav">
<li>
Page One
</li>
<li>
Page Two
</li>
</ul>
</div>
<div class="js-container">
</div>
<script type="text/template" id="One">
Color = <%- collection.models[0].attributes.color %>
</script>
<script type="text/template" id="Two">
Click the link "Page One" and check the console to see the fetch call for the collection incrementing.
</script>
var MyViewTwo = Backbone.View.extend({
el: '.js-container',
template: _.template( $('#Two').html() ),
initialize: function() {
this.render();
},
render: function () {
console.log('render Two');
this.$el.html( this.template() );
return this;
}
});
var MyRouter = Backbone.Router.extend({
routes: {
'': 'pageOne',
'pageone': 'pageOne',
'pagetwo': 'pageTwo'
},
pageOne: function () {
this.pageView(new MyViewOne());
},
pageTwo: function () {
this.pageView(new MyViewTwo());
}
pageView: function (view) {
this.view && this.view.remove();
this.view = view;
}
});
i think this issue related to a view not being disposed
you can change your router to do clean up on navigation
var MyRouter = Backbone.Router.extend({
currentViewOne : null,
currentViewTwo : null,
routes: {
'': 'pageOne',
'pageone': 'pageOne',
'pagetwo': 'pageTwo'
},
navigate: function () {
if (this.currentViewTwo) {
this.currentViewTwo.unbind();
this.currentViewTwo = null;
}
Backbone.Router.prototype.navigate.apply(this, arguments);
},
pageOne: function () {
this.currentViewOne = new MyViewOne();
},
pageTwo: function () {
this.currentViewTwo = new MyViewTwo();
this.currentViewTwo.render();
}
});
Related
Is it possible to handle F5 button in Backbone? Something like:
events: {
'click .btn': 'function1'
}
Actually I have problem with destroying models after refresh. I get error, when the method get("title") is invoking. And this is App.js. And I thought to create new function after refresh event.
var App = (function () {
var api = {
Views: {},
Models: {},
Collections: {},
Content: null,
Router: null,
init: function () {
Backbone.history.start();
return this;
}
};
var ViewsFactory = {
view1: function () {
api.Models.Model1 = new Model1();
if (!this.View1) {
this.View1 = new api.Views.View1({
el: $(".content"),
model: api.Models.Model1
}).on("trigger1", function () {
api.Models.Model1 = this.model;
api.Router.navigate("#test", {trigger: true});
});
}
return this.View1;
},
view2: function () {
api.Collections.Collection1 = new Collection1();
var test = new Model2({
title: api.Models.Model1.get("title"),
collection: api.Collections.Collection1
});
return this.View2 = new api.Views.View2({
el: $(".content"),
model: test
});
}
};
var Router = Backbone.Router.extend({
routes: {
"": "view1",
"test": "view2"
},
view1: function () {
var view1 = ViewsFactory.view1();
view1.render();
},
view2: function () {
var view2 = ViewsFactory.view2();
view2.render();
}
});
api.Router = new Router();
return api;
})();
I handle this by router and "" route.
I'm making a simple list of people with option when clicking on person's name the Router will take a name as a parameter 'student/:name' and find a right person's object in a collection. I instantiate collection in a GroupView class by fetching it from the server. And that's where the Error appears: to get the access to collection (so I can find right object) in my viewStudent() method in Router class, I'm making one more instance of GroupView(), and console shows an error and that's right, 'cause there're no objects in collection.
I cannot wrap my head around this, why in GroupView() I receive data from the server and my collection just works fine, but second time I instantiate GroupView() in a Router - there's no collection? Maybe there's any other way I can get access to the collection in my Router? Any help would be greatly appreciated.
var StudentModel = Backbone.Model.extend({
defaults: {
name: 'Volodya',
lastName: 'Peterson',
age: 22,
gender: 'male'
}
});
var StudentsCollection = Backbone.Collection.extend({
model: StudentModel,
url: '/students.json'
});
var StudentView = Backbone.View.extend({
tagName: 'li',
template: _.template($('#studentTpl').html()),
events: {
'click': function () {
eventAggregator.trigger('student:selected', this.model);
}
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var GroupView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new StudentsCollection();
this.collection.on('update', this.render, this);
this.collection.fetch();
},
render: function () {
var self = this;
this.collection.each(function (student) {
var studentView = new StudentView({
model: student
});
self.$el.append(studentView.render().el);
});
$('body').html(this.$el);
}
});
var RouterView = Backbone.View.extend({
tagName: 'ul',
render: function () {
var self = this;
_.each(this.model.toJSON(), function (value) {
self.$el.append('<li>' + value + '</li>');
});
return this;
}
});
var GroupController = function () {
this.start = function () {
var groupView = new GroupView();
};
};
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'student/:name': 'viewStudent'
},
index: function () {
groupController.start();
},
viewStudent: function (name) {
var groupView = new GroupView();
var selectedStudent = groupView.collection.find(function (student) {
return student.get('name') === name;
});
$('body').append((new RouterView({ model : selectedStudent})).render().el);
}
});
var eventAggregator= _.extend({}, Backbone.Events),
groupController;
$(function () {
var router = new Router();
groupController = new GroupController();
Backbone.history.start();
eventAggregator.on('student:selected', function (student) {
var urlpath= 'student/'+ student.get('name');
router.navigate(urlpath, {trigger: true});
});
});
I know Im pretty close to figuring this out. Im trying to filter out my collection based on if favorite eq true. If I console.log - I can see it's doing its job. But it's not updating my view.
Anyone have any idea what I'm missing or doing wrong?
Here is my code:
var Products = Backbone.Model.extend({
// Set default values.
defaults: {
favorite: false
}
});
var ProductListCollection = Backbone.Collection.extend({
model: Products,
url: '/js/data/wine_list.json',
parse: function(data) {
return data;
},
comparator: function(products) {
return products.get('Vintage');
},
favoritesFilter1: function(favorite) {
return this.filter(function(products) {
return products.get('favorite') == true;
});
},
favoritesFilter: function() {
return this.filter(function(products) {
return products.get('favorite') == true;
});
},
});
var products = new ProductListCollection();
var ProductListItemView = Backbone.View.extend({
el: '#wine-cellar-list',
initialize: function() {
products.bind('reset', this.render, this);
products.fetch();
this.render();
},
render: function() {
console.log(this.collection);
var source = $('#product-template').html();
var template = Handlebars.compile(source);
var html = template(this.collection.toJSON());
this.$el.html(html);
return this;
},
});
// Create instances of the views
var productView = new ProductListItemView({
collection: products
});
var CellarRouter = Backbone.Router.extend({
routes: {
'': 'default',
"favorites": "showFavorites",
"purchased": "showPurchased",
"top-rated": "showTopRated",
},
default: function() {
productView.render();
},
showFavorites: function() {
console.log('Favorites');
productView.initialize(products.favoritesFilter());
},
showPurchased: function() {
console.log('Purchased');
},
showTopRated: function() {
console.log('Top Rated');
}
});
$(function() {
var myCellarRouter = new CellarRouter();
Backbone.history.start();
});
There's many mistakes in your code, I'll try to clarify the most I can :
Your collection should be just like this :
var ProductListCollection = Backbone.Collection.extend({
model: Products,
url: '/js/data/wine_list.json',
comparator: 'Vintage' // I guess you want to sort by this field
});
Your view like this :
var ProductListItemView = Backbone.View.extend({
el: '#wine-cellar-list',
initialize: function() {
this.collection.bind('reset', this.full, this);
this.collection.fetch();
},
full: function() {
this.render(this.collection.models);
},
favorites: function(favorite) {
this.render(this.collection.where(favorite)); // here's the answer to your question
},
render: function(models) {
console.log(models);
var source = $('#product-template').html();
var template = Handlebars.compile(source);
var html = template(models.toJSON()); // You may have to change this line
this.$el.html(html);
return this;
},
});
And in your router :
showFavorites: function() {
console.log('Favorites');
productView.favorites(true); // or false, as you like
}
When I loads the page , it is getting all the datas and I am displaying the datas. But When I add a record, that is I am submitting the form "addcontact", the datas are creating in the database. But It is not adding into the collection and that this.collection.on('add') is not getting triggered. So, I think the problem was because of this. Can any one tell me that where I am doing wrong? Is there any other way to solve this.
This code works functionally, but the only problem with this is , on creating new record using this.collection.create({new post},{wait: true}); the values are getting updated in the database. But it is not adding into the collection.
(function(){
Backbone.emulateHTTP = true;
//Backbone.emulateJSON = true;
window.App = {
Models : {},
Collections: {},
Views : {},
Router : {}
};
window.vent = _.extend({},Backbone.Events);
window.template = function(id){
return _.template( $('#'+id).html() );
};
// Contact Model
App.Models.Contact = Backbone.Model.extend({
validate: function(attrs) {
if( !attrs.first_name ||
!attrs.last_name ||
!attrs.email_address) {
alert('Fill the missing fields');
}
}
});
// Collection
App.Collections.Contacts = Backbone.Collection.extend({
model: App.Models.Contact,
url : 'index.php/ContactsController/contacts'
});
// Global View
App.Views.App = Backbone.View.extend({
initialize: function(){
vent.on('contact:edit',this.editContact,this);
//console.log(this.collection.toJSON());
App.addContactView = new App.Views.AddContact({collection: App.Contacts});
App.allContactsView = new App.Views.Contacts({collection: App.Contacts});
$('#allcontacts').append(App.allContactsView.el);
}
});
// Add Contact View
App.Views.AddContact = Backbone.View.extend({
el: '#addcontact',
initialize: function(){
this.first_name = $('#first_name');
this.last_name = $('#last_name');
this.email_address = $('#email_address');
this.description = $('#description');
//this will fix it
this.collection.on("change", this.render , this);
},
events: {
'submit' : 'addContact'
},
addContact: function(e){
e.preventDefault();
this.collection.create({
first_name: this.first_name.val(), // <===== same as $this.el.find('#first_name')
last_name: this.last_name.val(),
email_address: this.email_address.val(),
description: this.description.val()
},{wait: true});
this.clearForm();
},
clearForm: function(){
this.first_name.val('');
this.last_name.val('');
this.email_address.val('');
this.description.val('');
}
});
// All Contacts Views
App.Views.Contacts = Backbone.View.extend({
tagName: 'tbody',
initialize: function(){
this.collection.on('add',this.addOne,this);
this.render();
},
render: function(){
this.collection.each(this.addOne,this);
//console.log(this.el);
return this;
},
addOne: function(contact){
var ContactView = new App.Views.Contact({model: contact});
//console.log(ContactView.render().el);
this.$el.append(ContactView.render().el);
}
});
// A view for a single View
App.Views.Contact = Backbone.View.extend({
tagName: 'tr',
template: template('allContactsTemplate'),
initialize: function(){
this.model.on('change',this.render,this);
},
render: function(){
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
})();
I have the following set of Views and Router, when the page is first displayed the 'welcome' view navigated too in the 'initialize' method for the Router is not visible - the render method is called but the element is not updated. If navigate to another view (hashbang) and then navigate back I can then see the initial view.
Does anyone know what I'm doing wrong?
var AppRouter = Backbone.Router.extend({
contentView: null,
routes: {
'welcome': 'welcomeAction',
'settings': 'settingsAction',
'books': 'booksAction',
'book/:id': 'bookAction'
},
initialize: function () {
this.navigate('welcome', true);
},
welcomeAction: function () {
this.contentView = new views.WelcomeView({ 'model': new models.Welcome() });
},
settingsAction: function () {
this.contentView = new views.SettingsView({ 'model': new models.Settings() });
},
booksAction: function () {
this.contentView = new views.BooksView({ 'model': new models.Books() });
},
bookAction: function (id) {
this.contentView = new views.BookView({ 'model': new models.Book({ 'Id': id }) });
}
});
function App() {
this.router = null;
this.initialize = function () {
this.router = new AppRouter;
Backbone.history.start();
};
};
var reader = new App;
reader.initialize();
The View is shown below:
WelcomeView: Backbone.View.extend({
'el': '#content-pane',
tagName: 'div',
template: _.template(templates.welcome),
events: {
},
initialize: function () {
_.bindAll(this, 'render');
this.renderLoading();
this.model.fetch({ success: _.bind(function () {
this.model.set({ Date: new Date() });
this.render();
}, this)
});
},
renderLoading: function () {
var html = _.template(templates.loading)();
$(this.el).empty();
$(this.el).append(html);
},
render: function () {
var html = this.template({ date: this.model.get('Date') });
$(this.el).empty();
$(this.el).append(html);
}
}),
Seems like a $(document).ready issue to me. Are you sure all the content of your page (specifically #content-pane) is loaded before you create the view?