I am building an application with Backbone and to go from step 1 to step 2 I make use the of the router.navigate function. Now it will go to the next page and go back etc.
However every step I take will be kept in the history and every page in the history will be visited after each event. This will also show up in the console log.
Now I am using require.js as well
Here is my router:
var OF = OF || {};
OF.SubscribeRouter = Backbone.Router.extend({
routes: {
"step/:stepNumber": "goToStep",
"*other": "defaultRoute"
},
goToStep: function(stepNumber) {
switch(stepNumber) {
case "1":
require(['./views/step1],function(Step1) {
OF.step1 = new OF.Step1;
OF.step1.render();
});
break;
case "2":
require(['./views/step2'],function(Step2) {
OF.step2 = new OF.Step2;
OF.step2.render();
});
break;
case "3":
require(['./views/step3'],function(Step3) {
OF.step3 = new OF.Step3;
OF.step3.render();
});
break;
case "4":
require(['./views/step4'],function(Step4) {
OF.step4 = new OF.Step4;
OF.step4.render();
});
break;
case "5":
require(['./views/step5'],function(Step5) {
OF.step5 = new OF.Step5;
OF.step5.render();
});
break;
case "6":
require(['./views/step6'],function(Step6) {
OF.step6 = new OF.Step6;
OF.step6.render();
});
break;
}
},
defaultRoute: function(other) {
//start loading the welcome page
this.goToStep(1);
}
});
Here is my main file which will start the router:
var OF = OF || {};
require.config({
paths: {
underscore: 'vendor/underscore-min',
jquery: 'vendor/jquery-2.0.3',
json2: 'vendor/json2',
backbone: 'vendor/backbone-min',
handlebars: 'vendor/handlebars',
router: 'routers/router'
},
shim: {
'backbone': {
deps: ['underscore', 'jquery', 'json2'],
exports: 'backbone'
},
'handlebars': {
deps: ['jquery', 'json2'],
exports: 'handlebars'
},
'templateReader': {
deps: ['jquery', 'json2', 'handlebars'],
exports: 'templateReader'
},
'router': {
deps: ['jquery', 'json2', 'backbone'],
exports: ''
}
}
});
require(['router'], function(SubscribeRouter) {
// create the application
'use strict';
OF = {
router: new OF.SubscribeRouter(),
};
//start router
Backbone.history.start();
});
And here is the view which will trigger the event for 'page change':
var OF = OF || {};
OF.Step1 = Backbone.View.extend({
el: '#content',
initialize: function() {
console.log("you're in the address view");
},
render: function() {
//save this in that ;)
var that = this;
OF.template.get('step1-step1', function(data) {
//set the source en precompile the template
var htmlSource = $(data).html();
var template = Handlebars.compile(htmlSource);
//fill template with object or ''
var compiled = template(OF);
//now place the completely compiled html into the page
that.$el.html(compiled);
});
},
events: {
"click #next": "nextStep"
},
nextStep: function() {
OF.router.navigate('step/2', {trigger: true});
}
});
So this is what I see in my console log after clicking next:
GET template-step2.html
now going back:
GET template-step1.html
So all seems well right now.
However now that I am back on step1 and click on next I expect to go to step2.
Unfortunately I will go to step3 and this is what I see in my console log:
GET template-step2.html
GET template-step2.html
GET template-step3.html
Is there a way to clear this history of some kind or to prevent this from caching or whatever it is it does?
where can I find a list of options for the router.navigate method?
I found the reason for this 'error' and it is because I use the same ID for the buttons in every template. When I changed this, the problem was solved.
Though I am still figuring out if the cache cannot be cleared so I can still use the same ID's (it is a next page now isn't it?).
Related
I've already checked the answers from people that have apparently the same problem but I haven't been able to fix my problem yet.
I have a view (teacher.profile.js) which is calling a collection (levels.js) which has a collection (subject.js), but when I load the page the subject collection is always undefined and said not to be a constructor.
After refreshing so many times, sometimes the second one, the subject collection is there and works.
Could anyone tell me what is wrong, please?
Thanks
subject.js
define(["GB"], function(GB) {
var subjectModel = GB.Model.extend({
idAttribute:"subjectId",
defaults: {
subjectId: '',
name: '',
levelId: '',
selected: false
}
});
return subjectModel;
});
subjects.js
define([
"GB",
"modules/register/teacher/models/subject"],
function (GB, SubjectModel) {
var subjectCollection = GB.Collection.extend({
model: SubjectModel,
url: "webapi/api/Administration/Subject"
});
return subjectCollection;
});
level.js
define(['GB', 'modules/register/teacher/models/subjects'], function (GB, SubjectCollection) {
var levelModel = GB.Model.extend({
defaults: {
levelId: '',
name: '',
iconClass: '',
subjects: ''
},
parse: function(attrs) {
**attrs.subjects = new SubjectCollection(attrs.subjects);** //Here is where the error is thrown. SubjectCollection is not a constructor.
return attrs;
}
});
return levelModel;
});
levels.js
define(["GB", "modules/register/teacher/models/level"], function (GB, LevelModel) {
var levelCollection = GB.Collection.extend({
model: LevelModel,
url: "webapi/api/Administration/Level"
});
return levelCollection;
});
teacher.profile.js view
define([
"GB",
"tpl!modules/register/teacher/teacherProfile", "modules/register/teacher/subject.level"
], function(GB, Template, SubjectLevelView) {
var view = GB.Views.Item.extend({
template: Template,
ui: {
subjects: "#subject-list",
levels: "#level-list",
infoTitle: "#info-title",
subjectsLevels: "#subjects-levels"
},
initialize: function() {
this.userLevels = [];
},
onRender: function() {
var self = this;
this.ui.infoTitle.text(GB.Localise("teacher-data-title"));
var levelsPromise = this.collection.fetch();
$.when(levelsPromise)
.then(function() {
var levelsSubjects = _.map(self.collection.models, function(item) {
if (item.get("subjects").length > 0) {
var view = new SubjectLevelView({ model: item });
self.userLevels.push(item);
return view.render().el;
}
});
self.ui.subjectsLevels.append(levelsSubjects);
});
}
});
return view;
});
main.js
require.config({
map: {
'*': {
'css': 'plugins/require-css/css',
'tpl': 'plugins/require.lodash.template', //this is our templating helper tpl!.html, its brings the template in already underscored, this is faster slightly than text! & subsequent template
'lodash': 'underscore'
}
},
paths: {
'plugins': '../plugins',
'styles':'../css',
'localisation':'core/localisation',
'jquery': '../lib/jquery-2.1.4',
'jquery.browser': '../plugins/jquery.browser',
'jquery.video': '../plugins/vide/jquery.vide',
'waypoints': '../plugins/waypoints/jquery.waypoints',
'backbone': '../lib/backbone',
'marionette': '../lib/backbone.marionette',
'text': '../lib/text',
'underscore': '../lib/lodash.underscore', //yes underscore is now lodash - its a better performer + extra features + no downside :)
'lodash': '../lib/lodash.underscore',
'bootstrap': '../lib/bootstrap',
'bootstrap-dialog': '../plugins/bootstrap-dialog/js/bootstrap-dialog',
'modernizr': '../lib/modernizr-2.8.3',
'backbone.validation': '../plugins/backbone-validation',
'themepunch.tools': '../plugins/rs-plugin/js/jquery.themepunch.tools.min',
'themepunch.rev': '../plugins/rs-plugin/js/jquery.themepunch.revolution.min',
'smoothscroll': '../plugins/SmoothScroll',
'json': '../plugins/requirejs-plugins/json',
'cldr': '../plugins/localisation/cldrjs/cldr',
'cldr-data': '../plugins/localisation/cldr-data',
'globalize': '../plugins/localisation/globalize/globalize',
'localise':'localisation/localise',
'GB': 'app'
},
shim: {
'marionette': {
deps: ['backbone'],
exports: 'Marionette'
},
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
},
'backbone.validation': {
deps: ['backbone', 'underscore']
},
'bootstrap': {
deps: ['jquery'],
},
'bootstrap-dialog': {
deps: ['bootstrap'],
},
'smoothscroll': {
deps: ['jquery.browser']
},
'themepunch.tools': {
deps: ['jquery']
},
'themepunch.rev': {
deps: ['themepunch.tools']
},
'jquery.browser': {
deps: ['jquery']
},
'waypoints': {
deps: ['jquery']
},
'jquery.video': {
deps: ['jquery']
},
'globalize': {
deps: ['cldr']
},
'json': {
deps: ['text']
}
}
});
require([
"GB",
"routes/application.router",
"bootstrap",
"core/validation"],
function (GB, AppRouter) {
GB.routers.application = new AppRouter();
GB.start();
});
app.js
define([
"marionette",
"core/GB.ini",
"globalize",
"localisation/localise",
"bootstrap-dialog",
"json!cldr-data/supplemental/likelySubtags.json",
"json!cldr-data/supplemental/plurals.json",
"json!cldr-data/supplemental/timeData.json",
"json!cldr-data/supplemental/weekData.json",
"json!localisation/messages/es.json",
"globalize/number",
"globalize/message",
"globalize/plural",
"modernizr",
"smoothscroll",
],
function (Marionette, AppIni, Globalize, Localise, Dialog, LikeSubtags, Plurals, TimeData, WeekData, Messages) {
var GB = window.GB = new Marionette.Application();
GB.User = {};
GB.routers = {};
Globalize.load(
LikeSubtags,
Plurals,
TimeData,
WeekData
);
Globalize.loadMessages(Messages);
Globalize.locale("es");
GB.Globalize = Globalize;
GB.Localise = Localise;
GB.Dialog = Dialog;
GB.Model = Backbone.Model.extend({});
GB.Collection = Backbone.Collection.extend({});
GB.Views = {
//if we do expand on these views they should probably get their own file.
Item: Marionette.ItemView.extend({}), //for a single model
Collection: Marionette.CollectionView.extend({}), //for a collection
Composite: Marionette.CompositeView.extend({}), //for a combination of a model and a collection
Layout: Marionette.LayoutView.extend({})
};
GB.session = new GB.Model();
GB.getUrl = function () {
return Backbone.history.location.origin + Backbone.history.location.pathname;
}
GB.getCurrentRoute = function () {
return Backbone.history.fragment;
};
GB.on("before:start", function () {
var RegionContainer = Marionette.LayoutView.extend({
el: "#app-container",
regions: {
header: "#header-wrapper",
main: "#main-region",
footer: "#footer-region",
dialog: "#dialog-region"
}
});
GB.regions = new RegionContainer();
});
GB.on("start", function () {
require(["modules/header/header.module"], function () {
GB.Header.Controllers.Overview.show();
});
require(["modules/footer/footer.module"], function () {
GB.Footer.Controllers.Overview.show();
});
AppIni.start();
Backbone.history.start();
if (GB.getCurrentRoute() === "")
Backbone.history.navigate("#home", { trigger: true });
});
return GB;
});
Foder Structure
enter image description here
Ok, my first guess was that parse method on the levelModel is never being called. But it should always be called on fetched. Moreover, I reread your question and saw that sometimes it did work for you.
So my second guess now is a race condition with requirejs. i.e: sometimes inside levels.js SubjectCollection is a real Backbone Collection and sometimes is undefined.
One of the reason for such a race can be Circular Dependencies. Can you share the full source? or verify that you dont have such a circulation?
I am working on a backbone application with multiple views. The navigation between the view is done by calling router.navigate('view', {trigger:true, replace:false}). I have a login page that has it own structure and the other views has another structure (very different). When I click on the login button the view changes along with the hash. The problem is when I click on the back button of the browser it doesn't go back to the login page but load the home page again, then I am required to click again on the back button in order to go back to the login page. In addition, when navigating to other pages sometimes the view handler isn't called when I click on the back button. I always use the router.navigate('view', {trigger:true, replace:false}) function to navigate between the views.
Here is my Router :
define([ "jquery", "backbone", 'views/header', 'views/sidePanel' ], function($,
Backbone, Header, sidePanel) {
var ApplicationRouter = Backbone.Router.extend({
_header : null,
_sidePanel : null,
routes : {
"" : "login",
"home" : "home",
"perspectives" : "perspectives"
},
initialize : function() {
this.firstPage = true;
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);
});
},
perspectives : function() {
var self = this;
require([ 'views/treePerspectivesView' ],
function(perspectivesView) {
self.changePage(new perspectivesView(), false);
});
},
changePage : function(page, noPanel) {
var deferred = $.Deferred();
console.log(page);
$page = $(page.el);
if (this.firstPage) {
if (!noPanel) {
self._sidePanel = new sidePanel({
el : ".left_col"
});
self._header = new Header({
el : '.top_nav'
})
$page.attr('class', 'right_col');
$page.attr('role', 'main');
$('.main_container').append($page);
} else {
$('body').append($page);
}
page.render();
} else {
if (!noPanel) {
$('.right_col').remove();
$('.right_col').unbind();
$page.attr('class', 'right_col');
$page.attr('role', 'main');
$('.main_container').append($page);
}else{
$('body').html($page);
}
page.render();
}
if (this.firstPage) {
this.firstPage = false;
}
deferred.resolve();
return deferred;
}
});
return ApplicationRouter;
})
This is how I navigate to the home view :
login:function(){
console.log("Login Clicked");
this.remove();
this.unbind();
router.fromBack=true;
router.navigate('home', {trigger: false, replace: false});
//router.home();
},
Is there a better way to navigate between the views to fix this problem (Maybe calling the changePage function with the has as a parameter) ?
How t fix the issue of the Back Button ?
Thank You.
I'd recommend you to use the count navigation hits for a router. Also you should create a back route for this job. Like this:
AppRouter.prototype.initialize = function() {
this.routesHits = 0;
Backbone.history.on('route', (function() {
this.routesHit++;
}), this);
};
AppRouter.prototype.back = function() {
if (this.routesHits > 1) {
this.routesHits = this.routesHits - 2;
window.history.back();
} else {
if (Backbone.history.getFragment() !== '/app') {
this.routesHits = 0;
}
this.navigate('/', {
trigger: true,
replace: true
});
}
};
Your changePage function:
// ...
changePage : function(page, noPanel) {
this.routesHit++;
// ...
}
And for using go back functionality you can use your back function:
appRouter.back()
For using this approach you should use a singleton object of the AppRouter.
Simple application-pilot with Backbone + requireJs.
In ie8 string Backbone.history.start({pushState: true}); leads to page reload every 20 seconds. Without it application doesnt start. What is the problem?
Below content of router.js :
define(
[
'jquery', 'underscore',
'backbone'
],
function ($, _, Backbone) {
var MainRouter = Backbone.Router.extend({
initialize: function () {
var re = new RegExp("(\/)+$", "g");
this.route(/(.*)\/+$/, "trailFix", function (id) {
// remove all trailing slashes if more than one
id = id.replace(re, '');
this.navigate(id, true);
});
},
routes: {
'home': 'showMainPage'
},
showMainPage: function (param) {
require([ 'views/global/main'], function (MainView) {
$(".navigation_item[data-type=home]").addClass("selected").on('click', function () {
return false;
})
$(".p_map, .p_feed").show();
new MainView();
});
}
});
var initialize = function () {
window.mainRouter = new MainRouter();
Backbone.history.start({pushState: true});
};
return {
initialize: initialize
};
});
This is fix for IE8
Backbone.history.loadUrl(window.location.pathname);
When I run my backbone app in NetBeans 7.3.1, the main page displays for a few seconds, maybe 5 or 6, then in NetBeans output I see the following...
Uncaught TypeError: Cannot read property 'View' of null (18:43:36:307 | error, javascript)
at (js/views/HomeView.js:6:28)
at d.execCb (js/libs/require/require.js:27:197)
at o (js/libs/require/require.js:10:471)
at (js/libs/require/require.js:12:184)
at o (js/libs/require/require.js:12:75)
at (js/libs/require/require.js:14:1)
at o (js/libs/require/require.js:12:75)
at l (js/libs/require/require.js:12:336)
at g.finishLoad (js/text.js:10:192)
at g.load (js/text.js:10:354)
at window.undefined.window.navigator.window.document.c.onreadystatechange (js/text.js:7:30)
Uncaught TypeError: Cannot read property 'Model' of null (18:43:36:317 | error, javascript)
at (js/models/Member.js:6:26)
at d.execCb (js/libs/require/require.js:27:197)
at o (js/libs/require/require.js:10:471)
at x (js/libs/require/require.js:15:186)
at m (js/libs/require/require.js:15:207)
at g.completeLoad (js/libs/require/require.js:21:388)
at d.onScriptLoad (js/libs/require/require.js:27:490)
Uncaught Error: Load timeout for modules: text!templates/homeTemplate.html
http://requirejs.org/docs/errors.html#timeout (18:43:38:511 | error, javascript)
at N (js/libs/require/require.js:7:217)
at A (js/libs/require/require.js:16:230)
at (js/libs/require/require.js:16:394)
It looks like RequireJS is failing to load Backbone. Here is main.js...
// Filename: main.js
require.config({
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
}
},
paths: {
jquery: 'libs/jquery/jquery-min',
underscore: 'libs/underscore/underscore-min',
backbone: 'libs/backbone/backbone-min',
templates: '../templates'
}
});
require([
'app',
], function(App) {
App.initialize();
});
I'm totally spinning my wheels on this. Why is Require not loading Backbone?
#Sushanth--: Edited original post to include HomeView.js
Here is the HomeView.js...
define([
'jquery',
'underscore',
'backbone',
'text!templates/homeTemplate.html'
], function($, _, Backbone, homeTemplate) {
var HomeView = Backbone.View.extend({
el: $("#page"),
initialize: function() {
},
render: function() {
var compiledTemplate = _.template( homeTemplate, {} );
this.$el.html( compiledTemplate );
}
});
return HomeView;
});
#Sushanth--: I'm rendering from the router.js...
// Filename: /js/router.js
define([
'jquery',
'underscore',
'backbone',
'views/HomeView',
'views/MembersView'
], function($, _, Backbone, HomeView, MembersView) {
var AppRouter = Backbone.Router.extend({
routes: {
// Define some URL routes
'members': 'showMembers',
// Default
'*actions': 'defaultAction'
}
});
var initialize = function(){
//alert('router init');
var app_router = new AppRouter;
app_router.on('route:showMembers', function () {
// Like above, call render but know that this view has nested sub views which
// handle loading and displaying data from the GitHub API
var membersView = new MembersView();
});
app_router.on('route:defaultAction', function (actions) {
// We have no matching route, lets display the home page
var homeView = new HomeView();
homeView.render();
});
// Unlike the above, we don't call render on this view as it will handle
// the render call internally after it loads data. Further more we load it
// outside of an on-route function to have it loaded no matter which page is
// loaded initially.
//var footerView = new FooterView();
//alert('hello from router.js');
//Backbone.history.start({pushState: true, root: "/modular-backbone/"});
//Backbone.history.start({pushState: true});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Added a test alert in main.js, app.initialize...
require(['app'], function(App) {
// THIS ALERT NEVER DISPLAYS!?!?!
alert('inside main.js before app.initialize');
App.initialize();
});
I replaced my Backbone and Underscore js files with the AMD versions and it started working.
I'm trying to integrate firebase login mechanisms into my phonegap application based on backbone.js and require.js.
I successfully set login with facebook and login with twitter and they seem to work properly.
Now i have problem with the last one: login with email and password... i don't know what's happening but is seems login with password doesn't call the callback method inside my authclient definition.
That's my main file i call with require at the beginning of the execution:
require.config({
paths: {
domReady: '../lib/require/domReady',
text: '../lib/require/text-1.0.6',
async: '../lib/require/async',
zepto: '../lib/zepto/zepto',
underscore: '../lib/underscore/underscore-min',
backbone: '../lib/backbone/backbone',
handlebars: '../lib/handlebars/handlebars',
firebase: '../lib/firebase/firebase',
backfire: '../lib/firebase/backfire',
fireauth: '../lib/firebase/firebase-auth-client',
leaflet: '../lib/leaflet/leaflet',
barcodescanner: '../lib/barcodescanner/barcodescanner',
templates: '../templates',
},
shim: {
'zepto': {
exports: '$'
},
'underscore': {
exports: '_'
},
'backbone': {
deps: ['zepto', 'underscore'],
exports: 'Backbone'
},
'handlebars': {
exports: 'Handlebars'
},
'firebase': {
exports: 'Firebase'
},
'backfire': {
deps: ['backbone','firebase'],
exports: 'Backfire'
},
'fireauth': {
deps: ['firebase'],
exports: 'Fireauth'
},
'leaflet': {
exports: 'L'
},
'barcodescanner': {
exports: 'Barcodescanner'
}
}
});
require(['zepto','domReady','underscore','backbone','firebase','fireauth','router'],
function ($,domReady, _,Backbone,Firebase,Fireauth,AppRouter) {
domReady(function () {
document.addEventListener("deviceready", run, false);
});
function run() {
firebaseRef = new Firebase('https://cicero.firebaseio.com');
authClient = new FirebaseAuthClient(firebaseRef, function(error, user) {
if (error) {
alert("error during user login");
} else if (user) {
auth = user;
Backbone.history.navigate("map", {trigger: true});
} else {
auth = undefined;
}
});
new AppRouter();
Backbone.history.start();
}
});
and this is a view in which i call login method:
define(["zepto", "underscore", "backbone", "handlebars","firebase","fireauth","text!templates/loginView.html"],
function ($, _, Backbone, Handlebars,Firebase,Fireauth,template) {
var loginView = Backbone.View.extend({
events: {
"touchstart #login" : "login",
"touchstart #register" : "showRegistration",
"touchstart #guest" : "showMap",
"touchstart #facebook" : "loginFacebook",
"touchstart #twitter" : "loginTwitter"
},
template: Handlebars.compile(template),
initialize: function () {
this.render();
},
render: function (eventName) {
$(this.el).empty();
$(this.el).html(this.template());
return this;
},
showRegistration: function () {
Backbone.history.navigate("register", {trigger: true});
},
showMap: function () {
Backbone.history.navigate("map", {trigger: true});
},
login: function(){
var user_email = $('#email').attr('value');
var user_password = $('#password').attr('value');
authClient.login("password", {
email: user_email,
password: user_password
});
},
loginFacebook: function(){
authClient.login("facebook");
},
loginTwitter: function(){
authClient.login("twitter");
}
});
return loginView;
});
As you can see i use 3 global var (firebaseRef, authCLient and auth) in order to get their references in every part of my application even if i don't know if it's a good way to do that.
If i try to login with facebook and twitter it works and callback function redirect me inside the new page, login with password and email instead makes a refresh of the page but it doesn't change it.