I am creating a simple Backbone app and the routing is not working. Here is my router.
define(function(require) {
'use strict';
var Backbone = require('backbone');
var Header = require('views/header.view');
var MainBody = require('views/main.body.view');
var Router = Backbone.Router.extend({
routes: {
"": "main",
"about/": "about"
},
main: function() {
var header = new Header();
$('#header').html(header.render());
var body = new MainBody();
$('#app').html(body.render());
},
about: function() {
console.log("About");
}
});
return Router;
});
I hit the / route as expected, but when I go to /about, it never hits the about function. Am I supposed to have a hash in the url somewhere? What else could I be missing that would cause this issue?
It's because routes are explicitly matched, so "about/": "about" will match /about/ and "about": "about" will match /about
There is a way to match both /about and /about/ and it requires using optional matchers (matcher) so your routes hash key will look like this
"about(/)": "about"
For hashless routes to work you need to run Backbone.history.start({pushState: true}).
According to backbonejs documentation, routers should be defined and addressed as follows:
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
Therefore, when you type http://<URL>/backbonepage#help, the help function will be invoked
Related
I am trying to fire a backbone event when the user inputs the URL (whatever.com)/questions/new.
my router looks like the following:
define(['backbone'],function(BackBone){
var routeExtend = Backbone.Router.extend({
routes: {
"": "home",
"questions/new": 'newQuestion'
},
});
var initialize = function(){
var Router = new routeExtend();
Router.on('route:newQuestion', function() {
console.log('new question');
});
BackBone.history.start();
return Router;
}
return {initialize:initialize};
});
And if I call router.navigate within my app, the event fires fine.
However, I want to be able to fire the "on" event if "questions/new" is the initial URL given to the web browser. Is there any way to do this?
you could define the function instead of router.on event
var routeExtend = Backbone.Router.extend({
routes: {
"": "home",
"questions/new": 'newQuestion'
},
newQuestion: function() {
console.log('new question');
}
});
I'm implementing an application which has two separate submodules within top level application module.
I have an admin module with a convention for routes to start with /admin and user module having routes that start with /user. Top level application defines a rootRoute so that when you navigate to http://url/ you are redirected to either admin or user page based on permissions. What i'm trying to understand is whether it is possible to start and stop specific modules based on the route. Here is an example of what i mean:
Let's assume i have a top level application (in coffeescript)
#ClientApp = do (Backbone, Marionette) ->
App = new Marionette.Application
navigate: (route, options = {}) ->
Backbone.history.navigate route, options
App.on "start", (options) ->
if Backbone.history
Backbone.history.start()
App.on "stop", ->
App.module("AdminApp").stop()
App.module("UserApp").stop()
class App.Router extends Marionette.AppRouter
initialize: (options) ->
#route /^admin(.*)/, 'startAdminApp', options.controller.startAdminApp
#route /^user(.*)/, 'startUserApp', options.controller.startUserApp
appRoutes:
"": "redirectToRoot"
App.addInitializer ->
new App.Router
controller: API
API =
redirectToRoot: ->
# some redirect logic that will lead you to either /admin or /user
startAdminApp: ->
App.mainRegion.show new App.Layouts.Admin
App.module("AdminApp").start()
startUserApp: ->
App.mainRegion.show new App.Layouts.User
App.module("UserApp").start()
App
Inside admin and user submodules i also have defined routes
#ClientApp.module "AdminApp.DashboardApp", (DashboardApp, App, Backbone, Marionette, $, _) ->
_.extend DashboardApp, Backbone.Wreqr.radio.channel("dashboard")
class DashboardApp.Router extends Marionette.AppRouter
appRoutes:
"admin/dashboard": "statistics"
API =
getLayout: ->
new DashboardApp.Layout.View
statistics: ->
DashboardApp.StatisticsAp.start()
DashboardApp.on "start", ->
#layout = API.getLayout().render()
API.statistics()
App.addInitializer ->
new DashboardApp.Router
controller: API
If i navigate to / the application works as expected, i'm redirected to necessary namespace and a specific sub-module is started. However if i define some other routes within a submodule, they seem to override the existing regexp matchers. So if i open the browser and navigate to /admin/statistics it will not start the admin application and the callback for /admin/statistics will fail with error. That is because the admin application won't start and the mainRegion is not filled with a corresponding layout. Note that the file containing top level application definition is required before any of the submodules (i guess that is why routes are overridden). I also understand that backbone router will invoke route callback when the first match is met.
So the question is whether it's possible to implement a kind of route manager that will check current route with a regular expression and start or stop the corresponding application (either admin or user) with all defined sub-routes being persistent and bookmarkable?
Had close task to resolve, haven't found any existing solution, so here is a small stub - project i've created
To resolve such task there are few problems to resolve :
1) Async routing. something like rootRouter should load app module and moduleRouter should call controller methods
2) Clear up backbone history handlers on module stop. The problem is even after module stop, route and handler still exist in BB history
So my hack, i mean solution :)
We need some router that will watch URL change and load module, Let it be ModuleManager
define(
[
'application'
],
function(App) {
App.module('ModuleManager', function(ModuleManager, Application, Backbone, Marionette) {
var currentPageModule = false,
stopModule = function(name) {
name && Application.module(name).stop();
},
startModule = function(name) {
Application.module(name).start();
};
ModuleManager.getModuleNameByUrl = function() {
var name = Backbone.history.getHash().split('/')[0];
return name ? (name.charAt(0).toUpperCase() + name.slice(1)) : 'Home'
};
ModuleManager.switchModule = function(name) {
if (!name) return;
stopModule(currentPageModule);
startModule(name);
currentPageModule = name;
};
ModuleManager.requireModule = function(name, callback) {
require(['apps/pages/' + name + '/index'],
callback.bind(this),
function() {
require(['apps/pages/404/index'], function() {
ModuleManager.switchModule('404');
})
}
);
};
/*
* this is key feature - we should catch all routes
* and load module by url path
*/
ModuleManager.FrontRouter = Marionette.AppRouter.extend({
routes: {
'*any': 'loadModule'
},
loadModule: function() {
var name = ModuleManager.getModuleNameByUrl();
ModuleManager.requireModule(name, function() {
ModuleManager.switchModule(name);
})
}
});
ModuleManager.addInitializer(function() {
new ModuleManager.FrontRouter;
});
ModuleManager.addFinalizer(function() {
delete ModuleManager.FrontRouter;
});
});
}
);
Great, that will load module with routes inside. But we'll get another problem - on sub module start we init its router, but we already routed to the page on sub-router init and URL still same. So sub-router will not be invoked till next navigation. So we need special router, that will handle such situation. Here is 'ModuleRouter':
App.ModuleRouter = Marionette.AppRouter.extend({
forceInvokeRouteHandler: function(routeRegexp, routeStr, callback) {
if(routeRegexp.test(Backbone.history.getHash()) ) {
this.execute(callback, this._extractParameters(routeRegexp, routeStr));
}
},
route: function(route, name, callback) {
var routeString = route,
router = this;
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (_.isFunction(name)) {
callback = name;
name = '';
}
if (!callback) callback = this[name];
// проблема - RouterManager уже стригерил событие route, загрузил саб-роутер.
// при создании саб роутера его колбэк уже вызван не будет, поскольку адрес страницы не изменился
// при добавлении роутов используется нативный ВВ route - который вещает колбэк на указанный фрагмент
// расширяем - если мы уже находимся на фрагменте на который устанавливается колбэк - принудительно вызвать
// выполнение обработчика совпадения фрагмента
/*
* PROBLEM : AppsManager already triggered 'route' and page fragments still same,
* so module router will not be checked on URL matching.
*
* SOLUTION : updated route method, add route to Backbone.history as usual, but also check if current page
* fragment match any appRoute and call controller callback
* */
this.forceInvokeRouteHandler(route, routeString, callback);
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
router.execute(callback, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});
return this;
},
// implementation destroy method removing own handlers anr routes from backbone history
destroy: function() {
var args = Array.prototype.slice.call(arguments),
routKeys = _.keys(this.appRoutes).map(function(route) {
return this._routeToRegExp(route).toString();
}.bind(this));
Backbone.history.handlers = Backbone.history.handlers.reduce(function(memo, handler) {
_.indexOf(routKeys, handler.route.toString()) < 0 && memo.push(handler)
return memo;
}, []);
Marionette.triggerMethod.apply(this, ['before:destroy'].concat(args));
Marionette.triggerMethod.apply(this, ['destroy'].concat(args));
this.stopListening();
this.off();
return this;
}
})
Please fill free to ask question or chat, i guess there are some point might need to be clarified.
In a Backbone.Router main router, I have "" route for my beginning route on app load. Its method decides to whether to create and render login view first or home view. When localhost:8080/indexapp is visited with no route it triggers the first route below, the empty "" route. After login and arriving homeview, url in address bar looks like localhost:8080/indexapp#. Form this point ordinary anchor hrefs referencing some dom elements currently available in the document does not lead to them. If I delete the hash at the end of the url and refresh the page, anchors work as intended.
What is the reason for that? What is the way to solve this issue?
mainrouter:
define([
'jquery',
'ratchet',
'underscore',
'backbone',
'forms/formsdatamodel',
'login/loginview',
'register/registerview',
'home/homeview',
],
function($, Ratchet, _, Backbone, formsDataModelInst, LoginView, RegisterView, HomeView){
var MainRouter = Backbone.Router.extend({
routes: {
"": "render_home",
"login": "render_login",
"register": "render_register",
},
initialize: function(){
this.listenTo( formsDataModelInst, 'sync', this.render_home );
},
render_home: function(){
if( formsDataModelInst.get('loginp') == true ){ //show you app view for logged in users!
this.homeView = new HomeView();
this.homeView.render();
}
else if ( formsDataModelInst.get('loginp') == false ){ //not logged in!
Backbone.history.navigate("/login", {trigger:true});
}
},
render_login: function(){ //display your login view
this.loginView = new LoginView;
this.loginView.render();
},
render_register: function(){ //display your register view
this.registerView = new RegisterView;
this.registerView.render();
},
});
return MainRouter;
});
My idea is to avoid empty url after # in address bar to overcome not working anchors. I do not know to make address bar url without # in hitting empty route. I have modified the main router to avoid empty url after # by:
associating "" route to a method, gate, that navigates to a route, "/home", that is associated with the method render_home_or_login that decides to render which.
routes now after modification:
routes: {
"": "gate",
"home": "render_home_or_login",
"login": "render_login",
"register": "render_register",
}
gate method added:
gate: function() {
Backbone.history.navigate("/home", {trigger:true});
},
This way the route will show like localhost:8080/indexapp#home at its bares case.
Avoiding emty url in address bar has solved the problem.
I have got three routes like this:
var appRouter = Backbone.Router.extend({
routes: {
"": "index",
"questionnaire/info/id/:id": "questionnaireInfo",
"questions/edit/*params": "questionEdit"
},
questionnaireInfo: function(id) {
$('#app-body').load('/dashboard/questionnaire/info/id/' + id);
},
questionEdit: function(questionnaireId) {
console.log(questionnaireId, params);
},
index: function() {
console.log('index');
}
});
And i initialize them like this:
var appRouting = new appRouter;
Backbone.history.start({
pushState: true,
silent: false,
root: '/dashboard/'
});
On first page load the route matches, it even console.log the proper messages. But i have got a link element like this:
Home Page
It doesn't match the "" route. And this href element doesn't match the "questionnaire/info/id/:id" route:
Load
How can i make this working? Thanks.
Maybe you're missing the '/' between '/dashboard' and each route
is index firing? in general the backbone docs recommend listing your routes from most specific to least specific. It's possible that the other roots aren't being called because "":"index" catches everything.
You need a trailing slash in your Backbone.history.start call for the root argument.
var appRouting = new appRouter;
Backbone.history.start({
pushState: true,
silent: false,
root: '/dashboard/'
});
You can see this in the example for Backbone.history in the docs.
I had the same problem. It seems that the links that call the routes have to be prepended by a "#" character. So if the root of the router is
/dashboard/
and your route is
"questionnaire/info/id/:id": "questionnaireInfo"
than the link triggering the route should be for example
Load
Hope this helps.
I have a router that does the site navigation nicely and also works when clicking the browsers back / forward button. However, when entering directly an URL I get 404.
Here is my router:
define(function(require) {
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone');
var AppRouter = Backbone.Router.extend( {
routes: {
'home' : 'homeHandler',
'webdesign' : 'webHandler',
'mobile' : 'mobileHandler',
'javascript' : 'javascriptHandler',
'hosting' : 'hostingHandler',
'contact' : 'contactHandler'
},
initialize: function() {
this._bindRoutes();
$('.link').click(function(e){
e.preventDefault();
Backbone.history.navigate($(this).attr("href"),true);
});
if(history && history.pushState) {
Backbone.history.start({pushState : true});
console.log("has pushstate");
}
else {
Backbone.history.start();
console.log("no pushstate");
}
console.log("Router init with routes:",this.routes);
},
homeHandler: function(e) {
require(['../views/home-content-view', '../views/home-sidebar-view'],
function(HomeContent, HomeSidebar) {
var homeContent = new HomeContent();
homeContent.render();
var homeSidebar = new HomeSidebar();
homeSidebar.render();
});
},
webHandler: function(e) {
require(['../views/web-content-view', '../views/web-sidebar-view'],
function(WebContent, WebSidebar) {
var webContent = new WebContent();
webContent.render();
var webSidebar = new WebSidebar();
webSidebar.render();
});
},
...
});
return AppRouter;
});
Obviously, I'm missing something.
Any clarification would be greatly appreciated.
Thanks,
Stephan
Backbone operates on a web-page (that has already been loaded in the browser). When you enter a URL in the browser directly, you're making a HTTP-request for that URL to the server. The server is not managed by Backbone. You have to define on the server the behavior when such HTTP-requests are encountered.