41

I want to have a 'catch all' route which runs when none of the other defined routes are matched. A type of 404 NotFound error handler.

I've tried adding this, which works but prevents other routes from matching:

this.route(/(.*)/, 'notFound', this.notFound);

Anyone solved this problem before?

evilcelery
  • 15,941
  • 8
  • 42
  • 54

7 Answers7

83

Answering here for completeness.

You can do this 2 ways. Define the regular expression using route(), as in the question. However, due to this bug you would need to define all your routes via the route() method, and in reverse order (catchall at top). This prevents you from using the routes hash, so (my) preferred method is:

routes: {
  'users/search': 'searchUsers',
  'users/:id': 'loadUser',

  '*notFound': 'notFound'
}

The key '*notFound' can actually be anything starting with *. You just require characters after the * to prevent a parsing error.

bcmcfc
  • 25,966
  • 29
  • 109
  • 181
evilcelery
  • 15,941
  • 8
  • 42
  • 54
  • 11
    This routing issue is a big slip-up in Backbone and [they're still doing it](https://github.com/documentcloud/backbone/blob/master/backbone.js#L932), the JavaScript spec says nothing about [object keys enumerating in source order](http://stackoverflow.com/a/10624559/479863) or any other particular order other than that it will be the same in various places. Depending on `routes` enumerating in any particular order is just asking for it. The only way to be sure is to use `route()` manually. – mu is too short Jun 28 '12 at 02:25
  • I don't know how I've not discovered this before, you're a god-send! – Matt Fletcher Feb 10 '14 at 15:35
  • Any ideas of what to do if you need regexps and are using `this.route`? I think @machineghost's answer below is probably a better solution. http://backbonejs.org/#History-start – clayzermk1 Mar 08 '14 at 01:30
  • How about if you have multiple routers? How does the ordering work? – Peter Ajtai Jul 18 '14 at 05:58
  • @PeterAjtai In my project i have multiple routers with one common router. This common router handle some general routes like this one about not found page. I wasn't faced with any kind of unexpected behavior. – Vlad Dekhanov Dec 10 '15 at 10:15
  • 1
    @muistooshort Yeoman does the same thing. In practice, I think relying on object key order is probably ok. Since it has become such a wide practice to rely on the order, I don't think browser/javascript implementations will ever not conform. – Kevin Wheeler Dec 23 '15 at 00:26
  • @KevinWheeler AFAIK ES6 specifies that objects are ordered by the insertion order of the keys too. – mu is too short Dec 23 '15 at 00:55
20

There's another, arguably simpler/more elegant way to solve this. Backbone.History.start() returns either true or false based on whether it matched a route or not. So, if you just do:

if (!Backbone.history.start()) router.navigate('404', {trigger:true});

instead of the usual:

Backbone.History.start();

it will have the same effect as the other answers.

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • How did this get buried? The `router.trigger` bit is out of date, but the logic is sound! http://backbonejs.org/#History-start – clayzermk1 Mar 08 '14 at 01:28
  • Feel free to upvote this answer if you think it should be un-buried :) – machineghost Mar 08 '14 at 01:42
  • 11
    But I believe Backbone.History.start is only called when the page loads the first time, so if you navigate to an invalid route without refreshing the page this won't work. – evilcelery Mar 08 '14 at 11:28
  • @machineghost If you bring your answer up to date such that it works, I would be happy to. I think `router.trigger('404')` needs to just be changed to `router.navigate('404', { 'trigger': true })` http://backbonejs.org/#Router-navigate @evilcelery You are correct that it is only called at page load, that would include direct location bar changes made by the user and URL navigation. The question is whether a hash change will trigger it. I need to do some refactoring before I can give it a shot. It probably doesn't work in all cases, but I'd like to find out what those cases are. – clayzermk1 Mar 10 '14 at 16:49
  • I've updated my example code. For the record, `router.loadUrl('404') will also work. – machineghost Oct 10 '14 at 17:22
  • 2
    @evilcelery, good point. However, what is a situation when your app would redirect a user to a page that does not exist? – gxc Oct 25 '14 at 18:28
  • Ideally your app's logic wouldn't do that :-). However, if you mess up, or if the user manually types in a bad URL, my solution wouldn't work. – machineghost Dec 27 '14 at 23:47
2

This very tiny plugin gets its job done: https://github.com/STRML/backbone.routeNotFound

It's the most elegant and robust way of solving this issue I've found so far, however please keep in mind that by using it, you are messing with Backbone's internals.

Pawel Dobierski
  • 407
  • 1
  • 4
  • 8
1

Without an example of your current routing code I would presume ensuring your catch all route is the last route should work for you

Dan
  • 1,513
  • 1
  • 24
  • 34
  • 1
    Looks like you actually need to add it as the _last_ route via router.route(), which is what the problem was. Seems like a bug, filed here: https://github.com/documentcloud/backbone/issues/1463 – evilcelery Jun 28 '12 at 00:48
  • 1
    @evilcelery: Yes, you must add routes using `route()` if you care about the order, Backbone (historically at least) makes some invalid assumptions about the iteration order of objects, there is no consistent ordering of object keys in JavaScript. – mu is too short Jun 28 '12 at 02:02
  • 1
    Oops I actually got the emphasised word wrong, turns out you currently needs to add it as the *first* route via `route()`. – evilcelery Jun 28 '12 at 02:09
  • 1
    @muistooshort Yeah I noticed that and thought it was strange they were using an object, seems like the routes should be defined in an array really – evilcelery Jun 28 '12 at 02:11
0

Just add it as the last route in the list. That way it will only be matched as the fallback option.

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
0

You can hack it in to backbone like this:

var _loadUrl = Backbone.history.loadUrl;
Backbone.history.loadUrl = function (fragment) {
    var result = _loadUrl.apply(Backbone.history, fragment);
    // the loadUrl returns false if no route was found.
    if(!result){
        // call 404 route on router if it exists.
        var handler = Backbone.history.handlers.filter( o => o.route.test("404") )
        if(handler.length) {
            Backbone.history.navigate("/404")

        }
    }
}

Then in your router you can do like this:

var Router = Backbone.Router.extend({
    routes: {
        "404": "pageNotFound"
    },

    pageNotFound: function () {
        alert("trigger pageNotFound route")
    }
});
winthers
  • 224
  • 4
  • 12
-18

I think this should not pass JavaScript to solve, should be the language to solve, for example php. On the server side processing of words, as long as you have the condition you can response head with error number 404

Myd
  • 103
  • 6
  • This concerns client-side routing using `hashs/hashbangs/pushstate`. Server side is not the answer. – Trevor Jun 28 '12 at 00:06
  • @Trevor The problem is not with the client if the server to interact, it is not a static page, be afraid of no significance. – Myd Jun 28 '12 at 00:09
  • 6
    I think you might want to read a little about client-side URL routing with Backbone before you try to answer questions about it: http://documentcloud.github.com/backbone/#Router – mu is too short Jun 28 '12 at 02:03