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
toplugins/router
- rename
viewAttached
toattached
router
Durandal 2 now has its own custom router, removing the Sammy dependency.
router.visibleRoutes
is nowrouter.navigationModel
router.allRoutes()
is nowrouter.routes
and is just an arrayroutes
no longer have acaption
norsetting
propertyrouter.navigateTo
is nowrouter.navigate
- remove sammy.js and update index.html
route array changes
url
is now renamed toroute
- because I use
router.makeRelative()
I can omit the full path to themoduleId
. SomoduleId: 'viewmodels/sessions',
becomesmoduleId: 'sessions'
visible
is now callednav
name
is nowtitle
- 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 beforeko.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()
(formerlyviewModel.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