Ember Router - How to handle 404 (Not Found) routes?
Asked Answered
K

3

14

I'm trying to figure out how to handle invalid routes within my application using Ember.Router.

Currently if I enter an invalid route, e.g. myapp.com/#FooBarDoesntExist, it will redirect to the index route ('/'). I'd like it if I could define a notFound or 404 state that it would route to so I can inform the user what happend. As opposed to them getting dumped on the home page.

Kimmi answered 8/10, 2012 at 17:15 Comment(0)
T
6

Ember.Router in its current version does not provide means to handle unknown routes. Time to hack!

Solution 1 - Quick and dirty

The idea here is the following. We have the Ember.Router.route(path) method, which is invoked with the requested (potentially unknown) path. After the invocation of this method, the path of the router is guaranteed to be known. So, if we compare the requested path and the actual path and they differ - then the requested path is invalid and we may redirect a user to the 404 page.

  App.Router = Ember.Router.extend({

    route: function(path) {
      this._super(path);
      var actualPath = this.get("currentState").absoluteRoute(this);
      if (path !== actualPath) {
        this.transitionTo("404page");
      }
    }
  });

This solution is quite expensive. For example, if the current state is "/a/b/c", and a user wants to navigate to "/b/d/e/unknown", the router will dutifully enter known states "b", "d" and "e", and only then we discard the path as unknown. It would be nice if we can tell this before the actual routing starts.

Solution 2 - Fiddling with private methods

Here we check the validity of the given path, and only then tell the router to proceed:

App.Router = Ember.Router.extend({

checkPath: function (path) {
  path = path.replace(this.get('rootURL'), '').replace(/^(?=[^\/])/, "/"); 
  var resolvedStates = this.get("states.root").resolvePath(this, path);
  var lastState = resolvedStates.get("lastObject");
  return lastState.match.remaining == "";
},

route: function(path) {
  if (this.checkPath(path)) {
    this._super(path);
  } else {
    this.transitionTo("404page");
  }
}
});

This solution also has its drawback - it uses the resolvePath method which is marked as private. Nevertheless, I'd use this solution, since it is more effective than the first one.

Twophase answered 12/10, 2012 at 19:6 Comment(3)
Neither of these solutions are absolutely ideal, but they're as close as it comes IMHO. Thanks for taking the time to put both of these together. Bounty earned.Kimmi
Fixed syntax errors, you do not use var on object propertiesProcambium
use mbreton's solution insteadSubjugate
B
19

A good way to handle this problem is to declare a route who map all possible urls in addition to your routes. You can an example here : http://jsfiddle.net/mbreton/r3C9c/

var App = Ember.Application.create();

App.Router.map(function(){
    this.route('detail', {path: "detail"});
    this.route('missing', { path: "/*path" });
});


App.MissingRoute = Em.Route.extend({
    redirect: function () {
        Em.debug('404 :: redirection to index');
        this.transitionTo("index");
    }
});

App.ApplicationView = Em.View.extend({
    didInsertElement:function(){
        $('#missingLink').on('click', function (e){
            window.location.hash = "#/pepepepepep";
            return false;      
        });
    }
});

In this example all unknown urls are redirect to index route.

Bosh answered 11/7, 2013 at 12:49 Comment(3)
Does anyone second this comment ^^ ? I ask because the original post and this are almost a year apart.Integrand
No, this doesn't work if you have nested resources. ie this will not catch /nestedResource/routeThatDoesNotExistTantalum
This is the default way to manage incorrect URLs as documented on emberjs.com/guides/routing/defining-your-routes/… and it works with nested resources as wellFirstborn
T
6

Ember.Router in its current version does not provide means to handle unknown routes. Time to hack!

Solution 1 - Quick and dirty

The idea here is the following. We have the Ember.Router.route(path) method, which is invoked with the requested (potentially unknown) path. After the invocation of this method, the path of the router is guaranteed to be known. So, if we compare the requested path and the actual path and they differ - then the requested path is invalid and we may redirect a user to the 404 page.

  App.Router = Ember.Router.extend({

    route: function(path) {
      this._super(path);
      var actualPath = this.get("currentState").absoluteRoute(this);
      if (path !== actualPath) {
        this.transitionTo("404page");
      }
    }
  });

This solution is quite expensive. For example, if the current state is "/a/b/c", and a user wants to navigate to "/b/d/e/unknown", the router will dutifully enter known states "b", "d" and "e", and only then we discard the path as unknown. It would be nice if we can tell this before the actual routing starts.

Solution 2 - Fiddling with private methods

Here we check the validity of the given path, and only then tell the router to proceed:

App.Router = Ember.Router.extend({

checkPath: function (path) {
  path = path.replace(this.get('rootURL'), '').replace(/^(?=[^\/])/, "/"); 
  var resolvedStates = this.get("states.root").resolvePath(this, path);
  var lastState = resolvedStates.get("lastObject");
  return lastState.match.remaining == "";
},

route: function(path) {
  if (this.checkPath(path)) {
    this._super(path);
  } else {
    this.transitionTo("404page");
  }
}
});

This solution also has its drawback - it uses the resolvePath method which is marked as private. Nevertheless, I'd use this solution, since it is more effective than the first one.

Twophase answered 12/10, 2012 at 19:6 Comment(3)
Neither of these solutions are absolutely ideal, but they're as close as it comes IMHO. Thanks for taking the time to put both of these together. Bounty earned.Kimmi
Fixed syntax errors, you do not use var on object propertiesProcambium
use mbreton's solution insteadSubjugate
N
2

The recommended way to do this in Ember 1.13 is to create a catch-all route:

Router.map(function () {
  this.route('home');
  this.route('login');
  this.route('404', { path: '/*path' });  // capture route in path
});

Then put your 404 template in 404.hbs.

Navarino answered 26/8, 2015 at 22:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.