Search code examples
asp.net-mvcsingle-page-applicationdurandalasp.net-mvc-areasdurandal-navigation

Multiple SPA Using MVC Areas - Navigate Outside SPA Router Routes


How can I escape client side routing when wanting to navigate outside the set list of routes setup in the router?

I created a project using Durandal.js and have created SPA's inside different Areas. The problem I ran into is that when I want to navigate outside the current SPA and into another or say back to the home page of the entire application which is not an SPA at all but simply a cshtml page.

What I have tried to do is use Durandal's mapUnknownRoutes handler to intercept and then use window.location.href to navigate out. This works, but when I want to go the home page of the application ("/"), the router matches the "root" of the SPA and doesn't navigate out to the home page but instead the SPA's root route.

The area route in this example is "/spaHome" whose MVC route looks like:

context.MapRoute(
           "spaHome_default",
           "spaHome/{*catchall}",
           new { controller = "Home", action = "Index", id = UrlParameter.Optional }
       );

Here's what I've done to set up the Durandal router:

var routes = [
    { route: ['spaHome', ''], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
    { route: 'one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#one/anotherPage"}
];

router.makeRelative({ moduleId: 'viewmodels' });

router.map(routes)
      .mapUnknownRoutes(function (instruction) {
          utils.navigateToPath(instruction.fragment);
          return false;
      })
      .activate({ pushState: true, root: "/spaHome" });

Any pointers or leads into the right direction for this would be much appreciated!


Solution

  • After some trial and error I was able to come up with a solution to what I was trying accomplish. It looks like a solid solution, so hopefully it doesn't cause any issues down the road.

    I changed the routes array and removed the default route of '' from the first route. This allowed me to have a unknown route to go off of when wanting to hit the normal MVC homepage. I also had to remove the "root" property from the activate function of the router. Which in turn meant I had to explicitly declare the routes in the route array with the extra area portion or the URL ("spaHome/").

    Then in my mapUnknownRoutes handler I checked the route for the area portion of the URL, and if that existed, I used the SPA router to show a notfound page for that SPA. Else I assumed that the route exists outside the area and I need to hard navigate to the URL using window.location.href.

    var routes = [
        { route: ['spaHome'], moduleId: 'spaHome', title: "spaHome", hash: "#spaHome"},
        { route: 'spaHome/one/anotherPage', moduleId: 'one/anotherPage', title: "one/anotherPage", hash: "#spaHome/one/anotherPage"}
    ];
    
    router.makeRelative({ moduleId: 'viewmodels' });
    
    router.map(routes)
      .mapUnknownRoutes(function (instruction) {
          if (instruction.fragment.toLowerCase().indexOf("spaHome/", 0) === -1) {
                utils.navigateToPath(instruction.fragment);
          } else {
                var notfoundRoute = "notfound";
                instruction.config.moduleId = notfoundRoute;
                history.navigate(notfoundRoute, { trigger: false, replace: true });
          }
      })
      .activate({ pushState: true });
    

    If anyone has a better solution please let me know!


    EDIT

    Ok, I ran into an issue with the history while doing this. I had to add a line to the Durandal router.js file to stop the previous route from being added to the history queue.

     if (!instruction.cancelQueue) {
                    router.trigger('router:route:before-config', instruction.config, router);
                    router.trigger('router:route:after-config', instruction.config, router);
                    queueInstruction(instruction);
                }
    

    Edit 2

    I also ran into an issue with this method where the navigation doesn't work quite right for IE9 and below.