Using the Backbone.js router to navigate through views modularized with require.js
I am separating my views and router into separate files with require. I then have a main.js file that instantiates the router, and also renders my default view.
My router has view ('View/:id') and edit ('Edit/:id') as routes. In main.js, when I instantiate the router, I can hardcode router.navigate('View/1', true) and the navigation works fine. In my view file, when I click on the edit link, I want to call router.navigate('View/' + id, true), but I'm not sure how I should do this.
I've had success calling Backbone.history.navigate('View/' + id, true), but I don't feel like I should be relying on the global Backbone object.
I tried passing ({ router: appRouter }) to my views so I could use this.options.router.navigate(), however that wasn't working for me.
In case you're curious, here's a bunch of code from my app:
Router:
define(['./View', './Edit'], function (View, Edit) {
return Backbone.Router.extend({
routes: {
'View/:id': 'view',
'Edit/:id': 'edit'
},
view: function (id) {
var model = this.collection.get(id);
var view = new View({ model: model });
view.render();
},
edit: function (id) {
var model = this.collection.get(id);
var edit = new Edit({ model: model });
edit.render();
}
});
});
View:
define(function () {
return Backbone.View.extend({
template: Handlebars.compile($('#template').html()),
events: {
'click .edit': 'edit'
},
render: function () {
//Create and insert the cover letter view
$(this.el).html(this.template(this.model.toJSON()));
$('#View').html(this.el);
return this;
},
开发者_开发技巧 edit: function () {
Backbone.history.navigate('Edit/' + this.model.id, true);
},
});
});
In case anyone else is looking for a solution to this problem like I was, I'm posting what I ended up doing. If you're using the boilerplate backbone.js, then you will have an initialize()
function in router.js
. I modified my initialize()
function to look like the following:
initialize = function () {
var app_router;
app_router = new AppRouter();
// Extend the View class to include a navigation method goTo
Backbone.View.goTo = function (loc) {
app_router.navigate(loc, true);
};
Backbone.history.start();
};
Due to backbone.js's particular flavour of inheritance, this allows allows me to call MyView.goTo(location);
from within any of my views.
As with pretty much any Backbone question, there are lots of ways to handle this. The way I approached it in my current project was to put everything in a global custom namespace, and use that to pass around the necessary references:
var MyNamespace = {};
MyNamespace.init = function() {
MyNamespace.appView = new MyAppView();
MyNamespace.router = new MyRouter();
// etc
}
Views could then refer to MyNamespace.router
as necessary. But it looks like this won't work/isn't encouraged with require.js, so here are some other options:
Don't ever call the router explicitly - instead, change a global state object that the router listens to. This is actually how I've done things in my current project - see this response for more details.
Attach the router to your top-level view, often called
AppView
, make that globally accessible, and useAppView.router.navigate()
.Make another module that provides a
navigate
utility function that callsBackbone.history.navigate()
internally. This isn't much different from what you're doing, but it would make it slightly more modular and keep you from using the global reference all the time. This also allows you to change the internal implementation.
You could do it the old fashioned way with window.location.hash :)
window.location.hash = "Edit/1"
Here's an alternate solution if you don't need explicit routes. When you app starts up create an object that extends Backbone Events
window.EventDispatcher = _.extend({}, Backbone.Events);
Then anywhere in you app you can listen for events
EventDispatcher.bind("mymodel:edit", this.editHandler, this);
And also from anywhere dispatch the event, data
below are any params you want to send along for the ride
EventDispatcher.trigger("mymodel:edit", data);
For me, the solution with goTo function worked with a slight change
Backbone.View.prototype.goTo = function (loc) {
appRouter.navigate(loc, true);
};
I know this question is old, but I am wondering why haven't you use require.js in order to get the router:
define(['./PathToRouter', ], function (router) {
return Backbone.View.extend({
template: Handlebars.compile($('#template').html()),
events: {
'click .edit': 'edit'
},
render: function () {
//Create and insert the cover letter view
$(this.el).html(this.template(this.model.toJSON()));
$('#View').html(this.el);
return this;
},
edit: function () {
router.navigate('Edit/' + this.model.id, true);
}
});
});
What about this approach? As backbone implements the template pattern in all its 4 components, with a little of design you can provide to each view an easy navigation mechanism through the app's router without needing to make any circular reference (this was something I saw in other similar posts, but try to avoid it).
Router component, not to much different from other router examples:
define('Router', ['backbone', ... ],
function (Backbone, ...) {
var MyRouter = Backbone.Router.extend({
routes: {
'viewA': 'viewA',
'viewB': 'viewB'
},
initialize: function () {
...
};
},
viewA: function () {
...
},
viewB: function () {
...
}
});
return MyRouter;
});
App, creates the router instance and fires the first view passing this instance:
define('App', ['backbone', ...
], function (Backbone, ...) {
function initialize() {
//route creation
if (!this.routes)
routes = new Router(this);
//backbone history start
Backbone.history.start();
//ViewA navigation, bigbang
if (!this.viewA)
this.viewA = new ViewA({router: this.routes});
this.viewA.render();
}
return {
initialize: initialize
};
});
BaseView, base constructor definition for all views and also a navigation method:
define('BaseView', ['jquery', 'underscore', 'backbone', ...
], function ($, _, Backbone, ...) {
var BaseView;
BaseView = Backbone.View.extend({
id: '...',
constructor: function (options) {
this.router = options.router;
this.model = options.model;
Backbone.View.prototype.constructor.call(this);
},
initialize: function () {
this.template = _.template(tpl);
},
events: {
},
render: function () {
$(this.el).html(this.template());
return this;
},
//Provides transparent navigation between views throught the backbonejs
//route mechanism
navigate: function(pageId)
{
this.router.navigate(pageId, {trigger: true});
}
});
return BaseView;
});
A View instance, each view extends from the base one instead of backbone, and inherits base behavior:
define('ViewA', ['jquery', 'underscore', 'backbone', 'BaseView'
], function ($, _, Backbone, BaseView) {
var ViewA;
ViewA = BaseView.extend({
id: '...',
constructor: function (options) {
this._super("constructor");
},
...
foo: function()
{
...
this.navigate("viewB");
}
});
return ViewA;
});
It works for me, and also it can be reuse in other projects.
for me i added an object to the main application like so;
define(['jquery','underscore','backbone','views/personView','views/peopleView','views/textView'],function($,_,backbone,PersonView,PeopleView,TitleView){
var Router = Backbone.Router.extend({
routes:{
'':'home',
'edit/:id':'editPerson',
'new':'editPerson',
'delete/:id':'deletePerson'
}
})
var initialize = function(){
var router = new Router();
window.app = {
router:router
}
router.on('route:home',function(){
})
//enable routing using '#'[hashtag] navigation
Backbone.history.start();
};
return {
initialize:initialize
};
});
and inside your view, you can say windows.app.router.navigate({'',trigger:true}) . Don't know if global scoping is good practice in this case, but it worked for me.
I have a new solution for routing AMD modules.
RequireJS Router https://github.com/erikringsmuth/requirejs-router
This takes the approach of lazy loading AMD modules as you navigate to each page. With the Backbone router you need to require all of your views as dependencies up front. This loads all of your apps Javascript on the first page load. The RequireJS Router lazy loads modules as you navigate to each route.
Example main.js used to run your app
define([], function() {
'use strict';
// Configure require.js paths and shims
require.config({
paths: {
'text': 'bower_components/requirejs-text/text',
'router': 'bower_components/requirejs-router/router'
}
});
// Load the router and your layout
require(['router', 'js/layout/layoutView'], function(router, LayoutView) {
var layoutView = new LayoutView();
// The layout's render method should draw the header, footer, and an empty main-content section
// then load the content section.
// render: function() {
// this.$el.html(this.template({model: this.model}));
// router.loadCurrentRoute();
// }
// Configure the router
router
.registerRoutes({
home: {path: '/', moduleId: 'home/homeView'},
order: {path: '/order', moduleId: 'order/orderView'},
notFound: {path: '*', moduleId: 'notFound/notFoundView'}
})
.on('statechange', function() {
// Render the layout before loading the current route's module
layoutView.render.call(layoutView);
})
.on('routeload', function(module, routeArguments) {
// Attach the content view to the layoutView's main-content section
layoutView.$('#main-content').replaceWith(new module(routeArguments).render().el);
})
.init({
// We're manually calling loadCurrentRoute() from layoutView.render()
loadCurrentRouteOnStateChange: false
});
);
);
精彩评论