Backbone.js route interceptor - backbone.js

I have main app with subapps:
main_app
|-mainRouter.js
|-sub_app
|-subAppRouter.js
subAppRouter.js extends mainRouter.js. subAppRouter.js has handler for route (e.g. /app1/item/). I have no access to subAppRouter.
Here is what I need:
In mainRouter I want to create routing that will handle all URL's from all apps.
It should handle route , make some check and in one case it should continue firing handler from subAppRouter for that url, else it should make redirect (e.g. /app2/somepage).
Could someone helps me with finding the best solution how to do it?
In other words: how to realize interceptor pattern via router in backbone?
Thanks

i will rephrase your question in points
1- you have a main router for common routes
2- you have a specialized router for some app routes
3- you need your main router to choose weather to handle the route of just forward it to sub router
to achieve this i suggest the following
1- create the main router , extending Backbone.Router
var mainRouter = Backbone.Router.extend({
routes:{
'index':'loadIndex',
//other common app routes......
},
loadIndex: function(){
//code to load index
}
});
2- then define the sub router for app,extending the main router, but notice how routes is defined
var subAppRouter = mainRouter.extend({
initialize: function(){
// here we will extend the base routes to not lose default routing, and add app special routing
_.extend(this.routes, {
'subApp/index': 'subAppIndex'
});
},
subAppIndex: function(){
// code to load sub app index
},
});
then you can use the sub router which will contains the base routing also

Here is a good article about subrouting. This works perfect for me.
Include subroute js lib in your project:
<script type="text/javascript" src="backbone.subroute.min.js"></script>
HTML body example:
App1
App2
<div class="app">
</div>
JS Code example:
var MyApp = {};
MyApp.App1 = {
Router: Backbone.SubRoute.extend({
routes: {
"": "init",
"sub1": "sub1"
},
init: function () {
console.log("app1");
$(".app").html($("<h1 />", {text: "App1"}));
$(".app").append($("<a/>", {href: "#app1/sub1", text: "sub1"}));
},
sub1: function () {
console.log("sub1");
$(".app").append($("<h2 />", {text: "sub1"}));
}
})
};
MyApp.Router = Backbone.Router.extend({
initialize: function () {
if(!MyApp.Routers){
MyApp.Routers = {};
}
},
routes: {
"app1/*subroute": "invokeApp1Router",
"app2": "app2"
},
invokeApp1Router: function (subroute) {
if(!MyApp.Routers.App1){
MyApp.Routers.App1 = new MyApp.App1.Router("app1/");
}
},
app2: function () {
console.log("app2");
$(".app").html($("<h1 />", {text: "App2"}));
}
});
$(document).ready(function () {
new MyApp.Router();
Backbone.history.start();
})

Related

Marionette js routing - What am I doing wrong here? I'm getting error that route actions are undefined?

Just want to get some basic routing up and going. Having seen a number of examples I thought the code below should work, but when I run I get the error "Unable to get property 'doChat' of undefined or null reference". Do I have the initialization sequence wrong?
require(["marionette", "jquery.bootstrap", "jqueryui"], function (Marionette) {
window.App = new Marionette.Application();
App.start();
App.addRegions({
//add some regions here
});
//Set up routing
var AppRouter = Marionette.AppRouter.extend({
appRoutes: {
"": "doDefault",
"chat": "doChat"
},
doDefault: function () {
alert("doing default...")
},
doChat: function () {
alert("doing chat...")
}
});
var router = new AppRouter();
//History
if (Backbone.history) {
Backbone.history.start();
}
})
The AppRouter allows two types of routes, standard backbone routes as defined in the routes property and routes which call functions in another object defined in the appRoutes property.
So to get you above code working, you can do one of two things. The quickest is simply to change the appRoutes property to routes which will do normal backbone routing. The second option is to create another object and pass that to the AppRouter as the controller during instantiation:
var myController = {
doDefault: function () {
alert("doing default...")
},
doChat: function () {
alert("doing chat...")
}
}
var router = new AppRouter({
controller: myController
});
This is detailed in the AppRouter documentation.

Ractive ,Components and routing

Let's say I have one root Ractive on the page,and various widgest to show when an hypothetic backbone router navigate to a route :
var widget1=Ractive.extend({template:"<div>{{foo}}</div>"});
var widget2=Ractive.extend({template:"<div>{{bar}}</div>"});
var view=new Ractive({
template:"<nav></nav><widget />",
components:{widget:widget1}
});
var Router=Backbone.Router.extend({/* the code ... */})
so widget1 would be shown when I navigate to /widget1 and widget2 when the route is /widget2,
What would be the best way to swap widgets depending on the current route without creating seperate root Ractives or hiding/showing widgets? thanks.
An alternative solution to my previous suggestion, which allows routes to be set in a more dynamic fashion (i.e. without having to declare them in a template up-front):
<nav>...</nav>
<!-- we have a single <route> component representing all possible routes -->
<route current='{{currentRoute}}'/>
This could be implemented like so:
Ractive.components.route = Ractive.extend({
template: '<div class="container"></div>',
init: function () {
this.container = this.find( '.container' );
this.observe( 'current', function ( currentRoute ) {
var View = routes[ currentRoute ];
if ( this.view ) {
this.view.teardown();
}
this.view = new View({
el: this.container
});
});
}
});
Then, to switch routes:
router.on( 'route', function ( route ) {
ractive.set( 'currentRoute', route );
});
With this approach all you'd need to do is register all the possible routes at some point in your app:
routes.widget1 = Ractive.extend({template:"<div>{{foo}}</div>"});
...and so on. If necessary you could interact with each view object by retrieving a reference:
route = ractive.findComponent( 'route' );
route.view.on( 'someEvent', someEventHandler );
One way would be to explicitly include the components representing each route in the top-level template:
<nav>...</nav>
{{# route === 'widget1' }}
<widget1/>
{{/ route === 'widget1' }}
{{# route === 'widget2' }}
<widget2/>
{{/ route === 'widget2' }}
Then, you could do something like:
router.on( 'route', function ( route ) {
ractive.set( 'route', route );
});
This would tear down the existing route component and create the new one.
If your route components had asynchronous transitions, you could ensure that the new route didn't replace the old route until any transitions had completed by doing this:
router.on( 'route', function ( route ) {
ractive.set( 'route', null, function () {
ractive.set( 'route', route );
});
});
(Note that as of version 0.4.0, ractive.set() will return a promise that fulfils when any transitions are complete - though callbacks will still be supported.)
Having said all that I'd be interested to hear about any other patterns that people have had success with.

Backbone pushstate history not working

I am using backbone.js routes and i am struggling to make history to work. Here is the code i have:
$(function() {
var AppRouter = Backbone.Router.extend({
routes: {
"/": "initHome",
"home": "initHome",
"projects": "initProjects",
"project/:id" : "initProject"
}
});
// Instantiate the router
var app_router = new AppRouter;
app_router.on('route:initProject', function (id) {
// Note the variable in the route definition being passed in here
getContent("project",id);
});
app_router.on('route:initProjects', function () {
getContent("projects");
});
app_router.on('route:initHome', function () {
getContent("home");
});
// SINGLE PAGE MAGIC
$(document).on("click",".links",function(e) {
var href = $(this).attr("href");
var url = lang + "/" + href;
page = $(this).attr("data-id");
var param = $(this).attr("data-param");
if (typeof(param) == 'undefined') { param = ""; }
if(activepage != href && !main.hasClass("loadingPage")){
loader.show();
firstInit = false;
activepage = href;
res = app_router.navigate(url, true);
getContent(page,param);
}
return false;
});
Backbone.history.start({pushState: true, root: "/karlin/"});
});
Push state is working fine on click, but it wont call getContent() function when i try back/next buttons in the browser. I am an newbie to backbone, so any advice will be helpful.
Change this: res = app_router.navigate(url, true);
To this: app_router.navigate(url, {trigger: true});
I can't see any reason to create a variable "res".
IMHO you've got a convoluted implementation of Backbone. I'd suggest moving your routes to the constructor like so:
var AppRouter = Backbone.Router.extend({
routes: {
"/": "initHome",
"home": "initHome",
"projects": "initProjects",
"project/:id" : "initProject"
},
initProject: function (id) {
// Note the variable in the route definition being passed in here
getContent("project", id);
},
initProjects: function () {
getContent("projects");
},
initHome: function () {
getContent("home");
}
});
// Instantiate the router
var app_router = new AppRouter;
Also, if you set up your routes properly like in the Backbone docs,
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
you can pass parameters to the routes with traditional links. You can also move your if activePage statement to the router as a helper function for changing pages.
Router.navigate is for rare instances.
I suggest, reading the Backbone docs over and over. I learn something new every time. There's a lot there and Backbone is doing things efficiently already. No need to reinvent the wheel.
Hope this helps!
I second Andrew's answer: your use of routing is a bit odd.
If you're interested in learning more about why, as Andrew says, "Router.navigate is for rare instances", read pages 32-46 here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf
It's part of the sample for my book on Backbone.Marionette.js, but routing concepts remain the same. In particular, you'll learn why the default trigger value is false, and why designing your app routing with that in mind will make your apps better.

Backbone routing doesn't work when converted to TypeScript

I'm trying to convert a basic Backbone.js router declaration to TypeScript.
var AppRouter = Backbone.Router.extend({
routes: {
"*actions": "defaultRoute"
},
defaultRoute: function () {
document.write("Default Route Invoked");
}
});
var app_router = new AppRouter();
Backbone.history.start();
My converted code is the following which doesn't work:
class AppRouter extends Backbone.Router {
routes = {
"*actions": "defaultRoute"
}
defaultRoute() {
document.write("Default Route Invoked");
}
}
var app_router = new AppRouter();
Backbone.history.start();
I get no compile time or runtime errors but the code does not function. Why?
I've had a look at Backbone.Router.extends and it isn't a basic prototype extension - so you can't just switch from Backbone.Router.extends to a TypeScript class extension.
I would change your TypeScript file to look more like your original JavaScript - you'll still get the benefit of intellisense and type checking - you just aren't using a class:
var AppRouter = Backbone.Router.extend({
routes: {
"*actions": "defaultRoute"
},
defaultRoute: function () {
document.write("Default Route Invoked");
}
});
var app_router = new AppRouter();
Backbone.history.start();
As Steve Fenton mentioned it's because Typescripts extend does not work in the same way as underscore / backones extend method.
The main problem is that the router calls _bindRoutes() before your routes field has been set in the "sub class" in type scripts hierachy.
A call to Backbone.Router.apply(this, arguments) in the constructor of your ts class as described by orad, ensures that this call will be made after the routes field has been set.
A manual call to this function will do the trick as well.
and just a FYI: call delegateEvents(this.events) in the constructor of your view classes if you want the dom events from your element to get triggered
Add all initialized fields in the constructor and make a call to super at the end:
class AppRouter extends Backbone.Router {
routes: any;
constructor(options?: Backbone.RouterOptions) {
this.routes = {
"*actions": "defaultRoute"
}
super(options);
}
initialize() {
// can put more init code here to run after constructor
}
defaultRoute() {
document.write("Default Route Invoked");
}
}
var app_router = new AppRouter();
Backbone.history.start();
The accepted answer doesn't seem to work with typescript 3.x . The super() method should be called before using this. Reordering code won't work because backbone is initializing routes within the super() method. Here is a version where the route configuration is directly passed to super().
class AppRouter extends Backbone.Router {
constructor() {
super({
routes: {
"*actions": "defaultRoute"
}
});
}
}

Backbone router.route isn't setting route

I have the following router defined, but the .route function doesn't seem to be setting. What am I doing wrong? Thanks, in advance, for the help.
# app.js.coffee
initialize: =>
router = new Backbone.Router
router.route "foo/:bar", "baz"
console.log router.routes # returns undefined
The routes you create using Router.route are stored internally in the History object -- they're not added to the Router.routes collection.
They still work though, see here for proof. Note that in this.routes, only the home route is defined, but you're still able to hit the baz route. You can see the baz route if you check Backbone.history.handlers, which is where the routes are actually stored.
var Router = Backbone.Router.extend({
initialize: function() {
this.route("foo/:bar", "baz");
},
routes: {
"": "home"
},
home: function() {
console.log("home hit");
},
baz: function(bar) {
console.log('test hit: ' + bar);
},
});
var router = new Router();
console.log(this.routes);
console.log(Backbone.history.handlers);
Backbone.history.start();
router.navigate("foo/testbar", { trigger: true });
​
Note though, I think you need to use this in your code, because router won't be defined yet inside initialize:
#route "foo/:bar", "baz"

Resources