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?
Related
I am new on backbone and using backbone with requirejs to make my first SPA.
When a user navigates to site.com/#report/1 an API call goes to site.com/app/report/1 and collects the data and renders the data in view page. Then, he goes to dashboard and navigates to site.com/#report/2 and another API call collects data of report 2 and displays as well. The problem is when I am using any event handler. In the view page, when anyone clicks on any event, in the callback it says that there is two JSON object saved as model. But it should be the second one only. The first report data also comes in console.log.
For your better understanding, I am giving the code of my router, model and view page.
Router
define([
'backbone',
'models/authlogin',
], function (Backbone, AuthloginModel) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'renderLoginPage',
'dashboard': 'renderDashBoard',
'report/:id': 'renderReportDetails',
'report/new': 'renderNewReportPage',
'members': 'renderMembersPage',
'member/:id': 'renderMemberPage',
'login': 'renderLoginPage',
'organization': 'renderOrganizationPage',
'programs': 'renderProgramsPage',
'program/:id': 'renderProgramPage',
'*path': 'errorpage'
},
errorpage: function () {
requirejs(['views/404', 'views/dashboard'], function (errorview, DashboardView) {
if ($('nav').length != 1) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
}
new errorview;
});
},
renderLoginPage: function () {
requirejs(['models/auth', 'views/login'], function (AuthModel, LoginView) {
var auth = new AuthModel;
new LoginView({model: auth});
});
},
renderDashBoard: function () {
requirejs([
'collections/reports',
'views/reports',
'views/dashboard',
'../../javascript/main',
], function (ReportsCollection, ReportsView, DashboardView, MainJS) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
var reports = new ReportsCollection;
reports.fetch({wait: true}).then(function () {
new ReportsView({collection: reports});
});
});
},
renderReportDetails: function (page) {
requirejs([
'models/reportdetails',
'views/reportdetails',
'views/dashboard'
], function (ReportDetailsModel, ReportdetailsView, DashboardView) {
var reportdetails = new ReportDetailsModel({id: page});
reportdetails.fetch().then(function () {
if ($('nav').length != 1) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
}
;
new ReportdetailsView({model: reportdetails});
});
});
},
renderNewReportPage: function () {
requirejs([
'collections/newreports',
'views/reports',
'views/newreports',
'../../javascript/main',
], function (ReportsCollection, ReportsView, DashboardView, MainJS) {
})
},
renderMembersPage: function () {
requirejs([
"collections/members",
"collections/invitations",
"views/members",
'views/dashboard'
], function (MembersCollection, InvitationsCollection, MembersView, DashboardView) {
var members = new MembersCollection();
members.fetch().then(function () {
var invitations = new InvitationsCollection();
invitations.fetch().then(function () {
if ($('nav').length != 1) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
}
new MembersView({collection: members, collection2: invitations});
});
});
});
},
renderOrganizationPage: function () {
requirejs([
'models/organization',
'models/orgMembers',
'views/organization',
'views/dashboard'
], function (OrgModel, OrgMembers, OrgView, DashboardView) {
var organization = new OrgModel();
var members = new OrgMembers();
organization.fetch().then(function () {
if ($('nav').length != 1) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
}
members.fetch().then(function () {
new OrgView({model: organization, model2: members});
})
})
});
},
renderProgramsPage: function(){
requirejs([
'collections/programs',
'models/program',
'views/programs',
'views/program',
'views/dashboard'
],function(ProgramsCollection,ProgramsModel,ProgramsView,ProgramView,DashboardView){
var programscollection = new ProgramsCollection;
programscollection.fetch().then(function(){
if ($('nav').length != 1) {
var authlogin = new AuthloginModel;
new DashboardView({model: authlogin});
}
new ProgramsView({collection: programscollection});
});
});
}
});
var initialize = function () {
var app_router = new AppRouter;
Backbone.history.start();
};
return {
initialize: initialize
};
});
Model
define([
'underscore',
'backbone'
],function(_,Backbone){
var ReportDetailsModel = Backbone.Model.extend({
urlRoot: '/app/report/',
idAttribute: 'id'
});
return ReportDetailsModel;
});
View
define([
'backbone',
'../../javascript/magnify_pop_up',
'text!templates/reportdetails.html',
'text!templates/reportcomments.html'
], function (Backbone, Magnify, ReportDetailsTemplate, RepCommentsTemplate) {
var ReportdetailsView = Backbone.View.extend({
el: '#container-2',
template: _.template(ReportDetailsTemplate),
initialize: function () {
this.render();
},
events: {
"click #reject": "reject",
"click #resolve": "resolve",
"click button.triage": "triage"
},
triage: function (e) {
e.preventDefault();
console.log(this.model.toJSON());
/* this.model.save({'status':1},{patch:true,success:function(model,response){
console.log('saved');
},error:function(model,response){
console.log('error');
}});*/
},
resolve: function (e) {
e.preventDefault();
this.model.save({'status':2},{patch:true});
},
reject: function (e) {
e.preventDefault();
this.model.save({'status':3},{patch:true});
},
render: function () {
$('#container').html(this.template(this.model.toJSON()));
this.$el.html(_.template(RepCommentsTemplate)(this.model.toJSON()));
return this;
}
});
return ReportdetailsView;
});
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 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();
}
});
I've created 2 separate views, 1 to render the template and the other one is where I bind the events, then I tried merging them into one in which case it causes an Uncaught TypeError: Object [object Object] has no method 'template'. It renders the template and the events are working as well, but I get the error.
edit.js, this is the combined view, which I think it has something to do with their el where the error is coming from
window.EditView = Backbone.View.extend ({
events: {
"click #btn-save" : "submit"
},
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
},
submit: function () {
console.log('editing');
$.ajax({ ... });
return false;
}
});
var editView = new EditView();
signin.js, this is the view that I can't merge because of the el being used by the ajax call and in SigninView's $(this.el) which causes the rendering of the templates faulty
window.toSigninView = Backbone.View.extend ({
el: '#signin-container',
events: {
"click #btn-signin" : "submit"
},
initialize: function() {
console.log('Signin View');
},
submit: function() {
$.ajax({ ... });
return false;
}
});
var toSignin = new toSigninView();
window.SigninView = Backbone.View.extend({
initialize: function() {
this.render();
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
and I use utils.js to call my templates
window.utils = {
loadTpl: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('templates/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
In my Router.js, this is how I call the rendering of templates
editProfile: function() {
if (!this.editView) {
this.editView = new EditView();
}
$('#global-container').html(this.editView.el);
},
utils.loadTpl (['SigninView', 'EditView'],
function() {
appRouter = new AppRouter();
Backbone.history.start();
});
I think that I figured out your problem.
First merge your views and delete the line var toSignin = new toSigninView();
Second modify your utils.js code like this :
window[view].prototype.template = _.template(data);
new window[view]();