Recently I upgraded a few projects from Durandal v1.x to Durandal 2.0. I did these before there were any notes on the Durandal site for upgrading so I took a bunch of notes. I was lucky enough that the first conversion was aided with the direct help of Rob Eisenberg, creator of Durandal. Rob has since created a great page in the docs to help you convert from 1.x to 2 which you can find here. But I also included all of my raw notes in this post. Rob’s is the definitive guide, but hopefully my brain dump may help some of you too :)

Please excuse the grammar below as it was a raw brain dump over the course of an hour.

Structure

This section covers all of the structural changes when I moved to Durandal 2.

Library Location

  • All files are now located in the Scripts/durandal folder
  • Plugins now include: dialog, history, http, observable, router, serializer, widget
  • Transitions are still in their own folder

Observable

The observable plugin takes any Plain Old JavaScript Object (POJO) and adds observability to it. Works on property by property basis. You can also use the API to subscribe, create computed properties or retrieve the underlying observable.

RequireJS Setup

Because Durandal is now moved out of the app, you need a requirejs.config setup:

requirejs.config({
    paths: { 
        'text': '../Scripts/text',
        'durandal': '../Scripts/durandal',
        'plugins': '../Scripts/durandal/plugins',
        'transitions': '../Scripts/durandal/transitions'
    }
});

RequireJS Changes

Durandal 2.x assumes no global libraries. It will ship expecting Knockout and jQuery to be defined with requirejs. The .NET templates by default will set them up as standard script libs and then register them with require as follows:

define('jquery', function () { return jQuery; });
define('knockout', ko);

No need to setup requirejs.config paths for this. Developers who wish to pipe everything through require.js now have that capability, but the default .NET starter kit will use the same approach as 1.x.

main.js

Remove from main.js app.AdaptToDevice()

Global replaces

  • Change paths for durandal plugins from durandal/plugins/router to plugins/router
  • rename viewAttached to attached

router

Durandal 2 now has its own custom router, removing the Sammy dependency.

  • router.visibleRoutes is now router.navigationModel
  • router.allRoutes() is now router.routes and is just an array
  • routes no longer have a caption nor setting property
  • router.navigateTo is now router.navigate
  • remove sammy.js and update index.html

route array changes

  • url is now renamed to route
  • because I use router.makeRelative() I can omit the full path to the moduleId. So moduleId: 'viewmodels/sessions', becomes moduleId: 'sessions'
  • visible is now called nav
  • name is now title
  • default route is now '' (empty string)

router parameter changes

Durandal v2.0 now passes in a parameter for each value. Query-string, if present, would be the last parameter.

In 1.2, activate used to pass an object.

//activate = function (routeData) {
activate = function (id, querystring) {
    //var id = parseInt(routeData.id);
    initLookups();
    return datacontext.getSessionById(id, session);
}

router.replaceLocation

replaceLocation now accepts a url and a second parameter, which is an object literal. We can pass a value for replace and set to true, if we want to replace the route history.

function goToEditView(result) {
    var url = '#/sessiondetail/' + session().id();
    router.navigate(url, {replace: true});
}

Lifecycle

activate

Activate no longer needs to return a promise and is automatically called for every composed object, without the need for an activator or additional configuration. Can remove return from all activate calls. If you return a promise, it will wait til the promise is done. But if we don’t return a promise, no need to return.

binding

  • formerly known as beforeBind
  • after activate, and immediately before ko.applyBindings is called
  • can also return applyBindings: false
  • can return object with instructions
// only this vm's view wont be cached
return { cacheViews: true } 

bindingComplete

Fires after the binding has happened.

attached

Formerly known as viewAttached.

function attached(view, parent){ ... }

compositionComplete

  • New event (almost was called documentAttached)
  • Fires whenever the composition that this module is participating in is entirely done (all parents and children compositions)
  • Fires from child bubbling up to parent
  • Useful for "measurement of an element", such as after the layout has been rendered
  • You can also use composition.addBindingHandler to register (or convert) a standard binding handler into one that runs after composition completion.

detached

New event that fires when the view has been detached from the DOM

canDeactivate

Fires before you deactivate a viewmodel, providing an opportunity to cancel. Only present with an activator.

canActivate

Fires before you activate a viewmodel, providing an opportunity to cancel.O nly present with an activator

deactivate

Fires when a viewmodel is being deactivated. Only present with an activator.

Activator

No need to return a promise from activate anymore. You get activate without an activator now, too. Three ways to get an activator:

  • router
  • dialogs
  • create and use one your self via activator.create() (formerly viewModel.activator())

Configuring Plugins

You can now configure plugins prior to app.start(). Some plugins want to do things at start-up, so this helps them. For example app.showDialog and app.showMessage are extended on the app module by including it in the configuration.

app.configurePlugins({
    router: true,
    dialog: true,
    widget: {
        kinds: ['expander']
    }
});

Router Binding

Faster, simpler, smaller. New router supports hash change and push-state. Better support for parameterized routes, splats, querystring parameters, and optional parameters. Deep linking supports is improved with custom callbacks and child routers. Lots of events are fired and tons of extensibility. Better support for “not found” routes and custom routing conventions. No SammyJS. Built from scratch with no dependencies outside of Durandal. Split into to modules: history and router. Don’t like our router semantics? Write your own on top of history without having to muck with browser stuff. ko.bindingHandlers.router gets installed with this plugin.



This is the same thing, just long-hand



This is a wrapper around the compose binding and makes binding router content simple.

Child Routing

We can nest routes. See examples for details

Activate Callback

Parameters come in as a list of parameters. Query-string comes in as the last argument (if present), and is keyed (object literal)

Mapping

Pass an array of objects with route, moduleId, title, nav, hash (and any custom data you want). visible is now called nav

Routing Sample

define (['plugins/router'], function (router) {
    return {
        router: router, 
        activate: function() {
            router.makeRelative({moduleId: 'viewmodels'});
        router.map([
            { route: '', 
                moduleId: 'helloindex', 
                title 'Hello World', 
                nav: 1},
            { route: '/bye', 
                moduleId: 'goodbye', 
                title 'See ya', 
                nav: 2}
        ]);

        //builds an observable model from the 
        //mapping to bind your UI to
        router.buildNavigationModel(); 

        //sets up conventional mapping for 
        //unrecognized routes
        router.mapUnknownRoutes(); 

        //activates the router
        return router.activate(); 
        // no longer needs a start module
    }
}

});

Use convention

useConvention has been replaced with router.makeRelative({moduleId: 'viewmodels'});

unknown routes

router.mapUnknownRoutes('moduleIdGoesHere'); 
//maps unknown routes to a single module

Or pass it a function

router.mapUnknownRoutes(function(instruction){
    // custom routing logic to implement 
    // your own conventions. simply configure 
    // the instruction object and the router 
    // will handle it
});

You can also define your own conventions. There will be a default (plugin config setting), but can override this.

router.on('router:route:before-config')
    .then(function (config) {
        config.moduleId = 'viewmodels/' + config.moduleId;
});

Compose

New features for the compose binding:

Inline Views

Can now compose an inline view:


    
Some inlined view bound against the model.

Templating Views

Can replace content inside of a template.


    

hello world

goodbye

Label the data-part attributes to get this, in the child views

// In the Child Views