I am working on a Backbone Application that contains multiple routes. I would like to modify my changePage function and make it take a hash as a parameter and when it is called it changes the page basing on that URL.
This is my router
define(["jquery","backbone",'views/header'], function( $, Backbone, Header) {
var ApplicationRouter = Backbone.Router.extend({
_header: null,
routes: {
"": "login",
"home": "home",
},
initialize: function() {
this.firstPage = true;
this.fromBack = false;
i18n.currentLocal = localStorage.getItem("currentLocal") || 'en';
Backbone.history.start();
},
login: function() {
var self = this;
require(['views/loginPageView'], function(loginView){
self.changePage(new loginView(),true);
});
},
home: function() {
var self = this;
require(['views/homePageView'], function(homeView){
self.changePage(new homeView(),false);
});
},
changePage: function (page,noPanel) {
var deferred = $.Deferred();
$page = $(page.el);
$page.attr('data-role', 'page');
console.log(this.firstPage);
if(this.firstPage){
$('#pageContent').append($page);
page.render();
if(!noPanel){
self._header = new Header({parent: $("#header")});
}
}
else{
$('#pageContent').html($page);
$("#header").empty();
page.render();
}
if (this.firstPage) {
this.firstPage = false;
}
deferred.resolve();
return deferred;
},
});
return ApplicationRouter;
})
This is the First View :
define([ "jquery", "backbone","text!../../pages/login.html"], function($, Backbone,loginTpl) {
var loginPageView = Backbone.View.extend({
events :{
"click #login" : "login",
"change input[type=radio]":"changeLanguage"
},
initialize : function() {
//Some Code
},
render: function() {
this.template = _.template(loginTpl);
$(this.el).html(this.template);
return this;
},
login:function(){
console.log("Login Clicked");
router.navigate('home', {trigger: false, replace: false});
},
changeLanguage:function(){
//console.log("change lang");
if(i18n.currentLocal == 'en'){
i18n.currentLocal='fr';
this.render();
$("#month2").attr('checked',true);
}else
{
if(i18n.currentLocal == 'fr'){
i18n.currentLocal='en';
this.render();
}
}
}
});
return loginPageView;
});
So instead of using router.navigate which caused some problems due to bugs in the router I think, I would like to use router.changePage('home.html');
and based on the URL I load to respective View.
Thank You
Related
In the router I do this
function test() {
self.topbarView = new TopbarView();
self.topbarView.render();
GhostviewHunter.addView(self.topbarView);
}
function clean() {
console.log(GhostviewHunter.currentViews.length);
GhostviewHunter.clean();
}
setInterval(test, 1000);
setInterval(clean, 1000);
ghostviewhunter should clean/remove the views:
define('ghostviewHunter', [], function() {
var GhostviewHunter = function() {};
GhostviewHunter.prototype.currentViews = [];
GhostviewHunter.prototype.addView = function(view) {
this.currentViews.push(view);
}
GhostviewHunter.prototype.clean = function() {
_.each(this.currentViews, function(view) {
view.remove();
});
this.currentViews.length = 0;
}
GhostviewHunter.__instance = null;
GhostviewHunter.getInstance = function() {
if( GhostviewHunter.__instance == null ) {
GhostviewHunter.__instance = new GhostviewHunter();
}
return GhostviewHunter.__instance;
}
return GhostviewHunter.getInstance();
})
TopView is fetching a model, the model is updated every 1seconde with setInterval function.
I thought that remove(); would be enough be the memory leak is very quick when I monitor the app.
Any idea ?
EDIT:
TOPBARVIEW
define('topbarView', [
'backbone',
'parameterManager',
'text!views/topbarView/topbarTemplate.html',
'drupalidModel',
'weatherModel',
'refreshTime',
'dateParser'
], function(Backbone, ParameterManager, TopbarTemplate, DrupalidModel, WeatherModel, RefreshTime, DateParser) {
var TopbarView = Backbone.View.extend({
el: '#topbar',
template: _.template(TopbarTemplate),
events: {},
initialize: function() {
var self = this;
_.bindAll(this, 'render', 'startDateRefresh');
this.dateParser = new DateParser();
self.startDateRefresh();
setInterval(self.startDateRefresh, RefreshTime.date);
this.initWeatherModel();
},
render: function() {
var self = this;
var data = {
picto_url : ParameterManager.get('WEATHER_RESOURCE_URL') + ParameterManager.get('WEATHER_PICTO_CODE') + ".png",
date: self.date
}
this.$el.html(this.template({data: data}));
},
initWeatherModel: function() {
var self = this;
var weather_url = ParameterManager.get('WEATHER_URL');
if(weather_url === null) {
this.drupalidModel = new DrupalidModel();
this.drupalidModel.fetch({
success: function(model, response) {
var center_id_num = model.get('center_id_num');
ParameterManager.set('DRUPAL_CENTER_ID_NUM', center_id_num);
ParameterManager.constructWeatherUrl();
self.model = new WeatherModel();
self.listenTo(self.model,'change', self.render);
self.startModelRefresh();
},
error: function() {
console.log("Failed to fetch center id!");
}
})
} else {
this.model = new WeatherModel();
self.listenTo(self.model,'change', self.render);
this.startModelRefresh();
};
},
startModelRefresh: function() {
var self = this;
this.modelRefresh = function() {
self.model.fetch();
}.bind(this);
self.modelRefresh();
setInterval(self.modelRefresh, RefreshTime.weather);
},
stopModelRefresh: function() {
var self = this;
clearInterval( self.modelRefresh );
},
startDateRefresh: function() {
var self = this;
this.date = this.dateParser.classicDate();
this.render();
}
});
return TopbarView;
})
As fbynite suggested, your code which is supposed to clear the interval(s) is not correct, you should pass the interval id to clearInterval.
apart from that, you're not calling stopModelRefresh() at all. You should make sure all external references are properly removed before removing the view. For example I've added a destroy method that clears the interval before removing the view:
var TopbarView = Backbone.View.extend({
el: '#topbar',
template: _.template(TopbarTemplate),
events: {},
initialize: function() {
},
render: function() {
},
modelRefresh: function() {
this.model.fetch();
},
startModelRefresh: function() {
this.modelRefresh();
this.intervalId = setInterval(_.bind(this.modelRefresh,this), RefreshTime.weather);
},
stopModelRefresh: function() {
clearInterval(this.intervalId);
},
destroy: function() {
this.stopModelRefresh();
this.remove();
}
});
Now your GhostviewHunter should call it instead of directly calling remove:
GhostviewHunter.prototype.clean = function() {
_.each(this.currentViews, function(view) {
view.destroy();
});
this.currentViews.length = 0;
}
or you can even override the remove method itself to something like:
remove: function(){
this.stopThisInterval();
this.stopThatInterval();
this.cleanUpSomethingElse();
Backbone.View.prototype.remove.call(this);
}
and have the ghost thingy call remove itself.
Note that you have other interval calling startDateRefresh which you're not even attempting to clear... You should clear all such similarly.
And as a side note, I strongly suggest to stop spamming self = this where it is totally unnecessary for eg:
stopModelRefresh: function() {
var self = this;
clearInterval( self.modelRefresh );
// Why..? Nothing here changes the context?
},
and I also suggest recursively calling modelRefresh once the current fetch succeeds/fails rather than calling it from an interval where you have no guarantee that the previous fetch is complete
I have a backbonejs application that contains a router file and some views , and also i'm using requirejs to add views to routes and add templates to views. here is my codes :
routes.js
var AppRouter = Backbone.Router.extend({
routes: {
"": "getLogin",
"login": "getLogin",
"register": "getRegister",
"forget-password": "getForgetPassword"
},
getLogin: function() {
require(['views/auth/loginView'], function(view) {
view = new this.LoginView();
});
},
getRegister: function() {
require(['views/auth/registerView'], function() {
view = new this.RegisterView();
});
},
getForgetPassword: function() {
require(['views/auth/forgetPasswordView'], function() {
view = new this.ForgetPasswordView();
});
},
});
var route = new AppRouter();
Backbone.history.start();
loginView.js
var LoginView = Backbone.View.extend({
el: '#wrapper',
initialize: function() {
NProgress.start();
this.render();
},
render: function() {
require(['text!partials/auth/login.html'], function(t) {
var json = { title: 'title', formName: 'frmLogin' };
var template = _.template(t);
$('#wrapper').html(template(json));
});
NProgress.done();
},
events: {
"click #btnLogin": "login"
},
login: function(e) {
e.preventDefault();
alert('some message');
}
});
also registerView.js and forgetPasswordView.js are similar to loginView.js.
now! when i change routes multiple times and hit #btnLogn it fires alert('some message'); function multiple times...!
Have you tried un-delegating the events in the view, on route change?
You could override the route method (annotated source) in your AppRouter and run it before each route is rendered.
route: function(route, name, callback) {
view.undelegateEvents();
return Backbone.Router.prototype.route.apply(this, arguments);
}
Note: Just an idea, not tested with your code
I'm not getting any attributes or options in model. I need to pass a route number to it in order to build a url. anyone see what im missing or how I should be doing this? I tried setting the attribute I want on the model but it's not in the model when I try to grab it.
view
define([
'text!html/tplDirection.html',
'models/direction',
'core'
], function (template, Direction) {
return Backbone.View.extend({
el: '',
template: _.template(template),
initialize: function (options) {
this.model = new Direction();
this.model.set({rtnm: options.routeNumber});
console.log(this.model);
},
setup: function (routeNumber) {
var self = this;
// self.model.set({rtnm: routeNumber});
$.when(self.model.fetch())
.done(function () {
console.log(self.model.toJSON());
self.render();
})
.fail(function (response) {
console.log(response);
console.log('request for data has failed');
});
},
render: function () {
var data = {
model: this.model.toJSON()
};
this.$el.html(_.template(template, data));
},
Model
define([
'core'
], function () {
return Backbone.Model.extend({
initialize: function (attributes, options) {
console.log(attributes);
},
/* model: Routes,*/
//url: '/apiproxy.php?method=getdirections&rt=',
parse: function (data) {
var parsed = [];
$(data).find('dir').each(function (index) {
var dir = $(this).find('dir').text();
parsed.push({
dir: dir,
});
});
return parsed;
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Model.prototype.fetch.call(this, options);
}
});
});
Solved by passing options to model on instantiating. What confused me is that they come through as attributes and not options in the model. How come?
view:
initialize: function (options) {
this.model = new Direction(options);
},
model:
initialize: function (attributes, options) {
console.log(attributes);
},
url: function () {
//'this' now contains attributes
var route = this.get("routeNumber);
//var route = this.attributes.routeNumber;
return '/apiproxy.php?method=getdirections&rt=' + route;
},
I added this to my backbone-extend.js file which resides in the same folder as backbone-min.js...
_.extend(Backbone.View.prototype, {
getFormData: function(form) {
var unindexed_array = form.serializeArray();
var indexed_array = {};
$.map(unindexed_array, function(n, i){
indexed_array[n['name']] = n['value'];
});
return indexed_array;
}
});
However, when I call this.getFormData in my view code, I get a method not defined error. What am I missing? Thanks for your help!
Edit: Here is my view. I have to uncomment the getFormData method to make it work. It can't see the getFormData otherwise...
define([
'jquery',
'underscore',
'backbone',
'models/Member',
'text!templates/memberEditTemplate.html'
], function($, _, Backbone, Member, memberEditTemplate) {
var MemberEditView = Backbone.View.extend({
el: $("#page"),
model: 'member',
initialize: function(args) {
this.member = new Member({ id: args.id });
this.member.on('error', this.eventSyncError, this);
this.member.on('sync', this.eventSyncModelLoaded, this);
this.member.fetch();
},
events: {
"click #bttnMemberSave": "bttnClickMemberSave"
},
eventSyncError: function(model,response,options) {
console.log('Sync error='+response.statusText);
$('#server-message').css({'color':'red', 'font-weight':'bold'}).text(response.statusText);
//$('#server-message').text(response.statusText);
},
eventSyncModelLoaded: function(model,response,options) {
this.render();
},
eventSyncModelSaved: function(model,response,options) {
console.log("Member saved!");
$('#server-message').css({'color':'green', 'font-weight':'bold'}).text("Member saved!");
//$('#server-message').text('Member saved!');
var to = setTimeout(function() { Backbone.history.navigate('members', true); }, 2000);
},
bttnClickMemberSave: function() {
var data = this.getFormData($('#member-form').find('form'));
this.member.save(data, { success: this.eventSyncModelSaved });
},
// getFormData: function(form) {
// var unindexed_array = form.serializeArray();
// var indexed_array = {};
// $.map(unindexed_array, function(n, i){
// indexed_array[n['name']] = n['value'];
// });
// return indexed_array;
// },
render: function() {
this.member.toJSON();
var compiledTemplate = _.template( memberEditTemplate, { member: this.member } );
this.$el.html( compiledTemplate );
return this;
}
});
return MemberEditView;
});
Ok, I added backbone-extend.js to the RequireJS required files array in my app.js, now it's working.
Already a couple of hours struggle trying to solve this...
Although the model gets fetched correctly and I can verify it as the view gets informed of the model's 'change' event, it just does not render.
At startup, the default model data ('Test Project'), is correctly displayed in the view, but after the model is refreshed, the view is not refreshed.
I tried to show a new view in the layout after model refresh but it did not change much...
Any idea or opinion about this ?
App.Project = function () {
var Project = {};
var ProjectModel = Backbone.Model.extend({
defaults:{
id: 0,
name: "Test Project",
intro: "",
desc: ""
},
initialize: function () {
// Flag fetch status to avoid multiple simultaneous calls
this.loading = false;
var self = this;
App.vent.on("project:display", function (_id) { self.fetchProject(_id); });
},
fetchProject: function (_id) {
if (this.loading)
return true;
this.loading = true;
var self = this;
var id = _id;
this.url = 'data.project_'+id+'.json';
this.fetch({
success: function (_data) {
self.loading = false;
},
error: function () {
self.loading = false;
}
});
}
});
Project.Details = new ProjectModel();
var Layout = Backbone.Marionette.Layout.extend({
template: "#project-layout",
regions: { details: "#project_details" }
});
Project.initializeLayout = function () {
Project.layout = new Layout();
App.content.show(App.Project.layout);
};
App.addInitializer(function () {
App.Project.initializeLayout();
});
Project.display = function () {
App.Project.Views.showDetails(Project.Details);
App.vent.trigger("project:display", 1);
}
return Project;
}();
App.Project.Views = function () {
var Views = {};
var DetailView = Backbone.Marionette.ItemView.extend({
template: "#project-details-template",
tagName: "div",
initialize: function () {
//this.listenTo(this.model, "change", this.render, this);
},
modelEvents: {
'change': "modelChanged"
},
modelChanged: function() {
console.log(this.model);
this.render();
}
});
Views.showDetails = function (_project) {
var projectView = new DetailView({model: _project});
App.Project.layout.details.show(projectView);
};
return Views;
}();
App.ProjectRouting = function () {
var ProjectRouting = {};
ProjectRouting.Router = Backbone.Marionette.AppRouter.extend({
initialize: function (_options) {
this.route('project/', "displayProject", _options.controller.display);
}
});
App.vent.on("project:display", function (_id) {
App.navigate("project/");
});
App.addInitializer(function (_options) {
ProjectRouting.router = new ProjectRouting.Router({
controller: App.Project
});
});
return ProjectRouting;
}();