Search code examples
javascripturl-routingsingle-page-applicationdurandal

Durandal Routing not working--am I missing a configuration setting?


I'm trying to get a Durandal app setup and I started with a very simple configuration with just two views.

What's happening is that when I call router.navigateTo('#/welcome') or router.navigateTo('#/primaryapplicants'), the view doesn't change. The URL in the address bar changes, however, and if I reload the page, I get the view I was trying to navigate to.

What am I doing wrong?

Here's a video demonstrating the behavior I'm experiencing. The code I referenced in the video is included below.

main.js

require.config({
    paths: { "text": "durandal/amd/text" }
});

define(function (require) {
    var system = require('durandal/system'),
        app = require('durandal/app'),
        router = require('durandal/plugins/router'),
        viewLocator = require('durandal/viewLocator'),
        logger = require('services/logger');

    system.debug(true);

    app.start().then(function () {
        // route will use conventions for modules
        // assuming viewmodels/views folder structure
        router.useConvention();

        // When finding a module, replace the viewmodel string 
        // with view to find it partner view.
        // [viewmodel]s/sessions --> [view]s/sessions.html
        // Otherwise you can pass paths for modules, views, partials
        // Defaults to viewmodels/views/views. 
        viewLocator.useConvention();

        // Will assume that a URL to #/something corresponds to a viewmodels/something viewmodel.
        // http://durandaljs.com/documentation/Router/)
        //router.mapAuto();

        var routes = [{
            url: 'welcome',
            moduleId: 'viewmodels/welcome',
            name: 'Welcome',
            visible: true
        }, {
            url: 'primaryapplicants',
            moduleId: 'viewmodels/primaryapplicants',
            name: 'Primary Applicants',
            visible: true
        }];

        router.map(routes);

        app.setRoot('viewmodels/shell');

        // override bad route behavior to write to 
        // console log and show error toast.
        // This method also accepts a "params" parameters, but we're not using it,
        // and to avoid JSLint warnings, we're not including it.
        router.handleInvalidRoute = function (route) {
            logger.logError('No route found', route, 'main', true);
        };
    });
});

shell.html

<div>
Shell HTML
<ul id="navbar">
    <li><a href="#/welcome">Welcome</a></li>
    <li><a href="#/primaryapplicants">Primary Applicants</a></li>
</ul>

        <!--ko compose: {model: router.activeItem} -->
        <!--/ko-->
</div>

shell.js

define(['durandal/system', 'services/logger', 'durandal/plugins/router'],
    function (system, logger, router) {


        function activate() {

            // Listen for click events so we can navigate
            $('body').on('click', '#navbar a', function (event) {
                var navTo = '#/welcome';
                navTo = event.target.hash;
                logger.log('Attempting navigation to ' + navTo,
                        null,
                        system.getModuleId(shell),//ignore jslint
                        true);
                router.navigateTo(navTo);
                //alert("About to navigate to " + event.target.hash);
                //router.navigateTo(event.target.hash);
            });


            logger.log('Shell loaded',
                null,
                system.getModuleId(shell),//ignore jslint
                true);

            return router.activate('welcome');
        }

        var shell = {
            activate: activate,
            router: router
        };

        return shell;

    }
);

Solution

  • As it often the case, once you figure out the solution, it's really quite simple.

    Apparently, in the compose binding (a custom Knockout binding provided by Durandal), you must have an afterCompose binding. In other words, this:

        <!--ko compose: {model: router.activeItem} -->
        <!--/ko-->
    

    Needed to be changed to this:

        <!--ko compose: {model: router.activeItem, afterCompose: router.afterCompose} -->
        <!--/ko-->
    

    Without it, my navigation wouldn't work. This is stated clearly enough in the docs, but I still missed it somehow:

    You must wire both the model and the afterCompose callback to the router in order for everything to work properly.