Upgrading to Durandal 2.0 | John Papa

John Papa

Evangelist on the loose

Upgrading to Durandal 2.0

...

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.

<!-- ko router: 
    {transition: 'entrance', cacheViews: true } -->
<!-- /ko -->

This is the same thing, just long-hand

<!--ko compose: {
         model: router.activeItem,
         compositionComplete: router.compositionComplete,
         attached: router.attached,
         cacheViews:true,
         transition: 'entrance'} -->
<!--/ko-->

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:

<!-- ko compose: { 
    model: 'viewComposition/inlineModule', 
    mode: 'inline' } -->
    <div>Some inlined view bound against the model.</div>
<!-- /ko -->

Templating Views

Can replace content inside of a template.

<!-- ko compose: { 
    model: 'viewComposition/inlineModule', 
    mode: 'templated' } -->
    <div data-part="header"><h1>hello world</h1></div>
    <div data-part="footer"><h1>goodbye</h1></div>
<!-- /ko -->

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

// In the Child Views
<h2 data-part="header"></h2>
<h2 data-part="footer"></h2>
tags: durandal javascript SPA
  • http://www.rowanbeach.com Steve Willcock

    Thanks for posting this, it’s a really useful overview of the changes. We recently converted a large project from Durandal 1 to 2 and this would have been quite helpful during that process – the official Durandal upgrade guide is very good (I wouldn’t have liked to try the process without having that for reference!) but this would certainly have been useful alongside it too.

    I did think that activate didn’t need to return a promise before the changeover to 2.0 either. We had some viewmodels which didn’t do this and they seemed to work in 1.0 (although they activated immediately of course). We generally find that returning a promise from activate can make view bindings simpler where you are loading remote data (or indeed doing anything async) as there’s less of a need to put if / with conditionals in the bindings – you can generally assume the data is all there by the time the view binds unless an error has occurred in which case the promise can be rejected. When not doing async stuff on activate a promise is an overhead you don’t need though of course.

    Thanks

    Steve

  • Federico

    John,

    How can I generate a drop-down menu on Durandal 2.0?

    var rutas = ko.computed(function () {
    return router.allRoutes().filter(function (r) {
    return r.settings.tienda;
    });
    });

    This used to work (I got if working after following your Pluralsight course). I now have Durandal 2.0, and have the following code:

    var rutas = ko.computed(function () {
    return router.routes.filter(function (r) {
    return r.tienda;
    });
    });

    And I’ve set-up “tienda: true” for each route I want to display on my menu. But it isn’t working..
    I know that router.routes is now a normal array, so how can I make this work?

    • ed

      did you figure out how to fix this dropdown menu issue?

      • Peter

        I have the same issue. It would be super if anybody could help on this. Many thanks

  • Murray

    Thanks so much for publishing this.

    Was struggling getting your course to work with out it in the new version!

  • http://www.measurebox.com Andrew

    Hey Scott,

    Any plans to update the pluralsight course and code to reflect this?

    Thanks,
    Andrew

    • John

      Andrew – Not yet. I’m going to approach them about adding some content to it to upgrade to Durandal 2. But first, I have to finish my current course work.

      • http://www.measurebox.com Andrew

        OK – I guess I will move onto the Single Page Apps with HTML5, Web API, Knockout and jQuery one until that gets sorted out.
        Thanks for your response!
        Andrew

  • http://www.measurebox.com Andrew

    My apologies! I called you Scott because I had Hanselman up on the code camper app.
    But would still like to know if there are any plans afoot to update the Single Page Apps JumpStart Plurasight course and code to reflect the changes in Durandal.
    Thanks,
    Andrew

  • Doug

    Oops! I’m glad I came to Papa (pun intended). I’m beating my head bloody on my desk, in Durandal 2 creating an Admin setting for routes. And here I find out setting property has been removed with Sammy.

    Now I know my last hour has been entirely futile. /eye twitching erratically/

  • http://www.medapp.co.za Peter

    Hi John

    Any chance you will be upgrading your templates to reflect durandal 2.0 (Hot Towelette with Durandal 2.0?)

    Thanks

    Peter

  • Jeremy

    Doug, I had that same problem. I found that if you specify a value for Setting in every single route (even if that’s null or empty) the authorisation code will still work. I guess the difference now is that you can use any key value pair when defining the route, but because they (including Settings) is not defined by default, you need to use that key in all your routes or have a way of catching the key when it is undefined.

    Jeremy

  • Adel Sal

    Hey John,
    I’m inspired by codeCamper so that does count me as one of your fans:)
    I have managed to modify it to suit the new Durandal 2.0, Breeze.web.api2 and entity framework 6.0.. The only thing I can’t figure out; how can I replace the “settings : caption” for including icons in navigation buttons??

    Thanks,
    Adel

    P.S on your post;
    Typo: replaceLocation now accepts a url and a second parameter

    Should be: navigate now accepts a url and a second parameter

    • http://serverleader.com serverleader

      hey adel! can you post the codeCamper project updated somewhere to download it ? thanks

      • Adel Sal

        I will post it in github soon.. Sorry for the late response.

  • Barak Poker

    Where exactly are we supposed to add the 2 lines of code mentioned in the “RequireJS Changes” section?

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

    Thanks,

    Barak

    • John

      Barak – In the main.js file after the require.config code and before the boot logic.

  • Rodrigo Brito

    Great Post!

  • mg1075

    The conversion guide link for Durandal is broken;
    now found at…

    http://durandaljs.com/documentation/Conversion-Guide.html

%d bloggers like this: