Getting Started

Project setup, structural overview and brief explanation.

Setup

Durandal works with any backend technology...or no backend technology. All you need to get going are the necessary script libraries, modules and folder structure. To get started, choose an option depending on which platform you are most comfortable with:

.NET

If you are coming from a .NET background or using Visual Studio, you've got two options.

  1. If you have VS2012 and the 2012 web tools installed, you can grab the VSIX file and install it. Then, start Visual Studio and choose to create a new 'ASP.NET MVC 4 Web Application'. After selecting MVC, you will have the option to choose which template you want. Choose 'Durandal SPA Template'.
  2. Durandal is also available through nuget. You can install the entire starter kit with the following command: Install-Package Durandal.StarterKit

Node.js

For those comfortable with Node.js, we highly recommend using our Mimosa skeleton. Here's how Mimosa describes itself:

A modern browser development toolkit. JavaScript, CSS, and template compilers, linting, optimization, serving, RequireJS support, and Live Reload built right in. Pluggable for authoring your own functionality.

In short, it's an amazing development and build tool for JS applications. If you wish to use it with Durandal, these steps will get your system set up:

  1. Install Node.js
  2. From the command line execute: npm install -g mimosa@0.9.0
  3. Once mimosa is installed, execute: mimosa mod:install mimosa-skeleton
  4. After this is all set up, creating a new Durandal project is easy-peasy. From the command line execute: mimosa skel:new durandal path/to/your/new/project/folder

Anyone and Everyone

In the end, Durandal is just a collection of JavaScript libraries, so you don't need anything special to use it. Hop on over to our github repo and fork away. You can just grab the contents of the App folder and use it to kickstart your own project.

Overview and Explanation

The Durandal StarterKit sets up a basic navigation-style architecture for you along with a couple of basic screens. Adding your own screens is as simple as creating modules and views, putting them in the proper location and registering them with the router. Let's see how this application is put together...

Organization

If you expand the App folder, you will find the source for the entire SPA sample. Here's the high level organization you will find:

Durandal applications are built as a collection of AMD modules. In fact, Durandal itself is just a set of modules. All the core modules can be found in the durandal folder. The viewmodels and views folders contain the application-specific code. In your own application, you can organize your app-specific code in any way that makes sense to you. For purposes of this sample, we've located our view models and views in folders thusly-named (a common convention). Finally, much like a native application, your app execution always starts with main which is referenced in the index.html (.cshtml for .NET).

index.html

The index.html has all the things you would expect, such as meta, css links and 3rd party script references. The interesting part is the body:

<body>
  <div id="applicationHost"></div>
  <script type="text/javascript" src="/App/durandal/amd/require.js" data-main="/App/main"></script>
</body>

The applicationHost is where your app's views will live. We'll talk about how that happens a bit more in the next section. Below that is the script tag that references RequireJS. It points to our application's entry point, declared in the data-main attribute. At runtime, this resolves to the main.js file.

main.js

The main.js module is the first code that gets executed and it is where you configure Durandal and tell it to start up the app. Let's look at the main module and see what we can learn:

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

    system.debug(true);

    app.start().then(function () {
        viewLocator.useConvention();
        
        router.useConvention();
        router.mapNav('welcome');
        router.mapNav('flickr');

        app.adaptToDevice();
        app.setRoot('viewmodels/shell', 'entrance');
    });
});

The most important thing to learn from this example is that all app-specific code is written as modules. There is one module per file and each module declares itself by calling define. It can then require other modules in the application by referencing their path. In this example, we can see that our main module is dependent on four other modules: app, viewLocator, system and router.

The next thing of note is the call to system.debug(true);. Durandal's system module has a log function which it uses to output important insights into the working of the framework. This log implementation is cross-browser and will only be output when debugging is turned on, as it is here. This logging information can help you track down issues with your code as well as give you a deeper understanding of how Durandal works. It is also handy for use in your own app-specific code.

In order to kick things off, we call app.start() which returns a promise. The promise resolves when the DOM is ready and the framework is prepared for configuration. At that point we set up our viewLocator and router with basic conventions and routing info. Then, we call app.adaptToDevice(); to make sure that our app acts like a SPA, especially on mobile devices. Finally, we call app.setRoot(…). This is what actually causes the DOM to be composed with your application. It points to your main view model (or view). When this is called, Durandal's composition infrastructure is invoked causing RequireJS to require your root view model, use the viewLocator to locate its view, data-bind them together and inject them into the applicationHost element. Additionally, the 'entrance' transition animation is used to animate the app in.

The code described above differs from app to app, but usually your main.js will follow the same simple steps every time:

  1. (Optionally turn on debugging).
  2. Call app.start().
  3. Configure your app-specific conventions (and optionally routing too).
  4. Configure 3rd party libraries.
  5. Set your application's root.

The Shell

Every application has a shell/window/layout or similar structure. We set that by calling setRoot as described above. Typically, you will have both a code and view component to your shell, as is demonstrated in our template. Let's look at simplified versions of those to see how they work:

shell.js
define(function(require) {
    var router = require('durandal/plugins/router');

    return {
        router: router,
        activate: function () {
            return router.activate('welcome');
        }
    };
});
shell.html
<div>
    <div class="navbar navbar-fixed-top">
        <div class="navbar-inner">
            <ul class="nav" data-bind="foreach: router.visibleRoutes">
                <li data-bind="css: { active: isActive }">
                    <a data-bind="attr: { href: hash }, html: name"></a>
                </li>
            </ul>
        </div>
    </div>
    
    <div class="container-fluid page-host">
        <!--ko compose: { 
            model: router.activeItem,
            afterCompose: router.afterCompose,
            transition:'entrance'
        }--><!--/ko-->
    </div>
</div>

When you call setRoot, Durandal requires both the module and the html and uses Knockout to data-bind them together. It then injects them into the DOM’s applicationHost. If you look at the module, you will see that we have exposed the router as a property called router. Then, look at the html for the "nav bar" and you will see that we are dynamically generating our navigation structure based on the router's visibleRoutes array. Below that we have a container where our pages will be switched in and out. How does that work?

Durandal takes Knockout's data-binding implementation and layers a powerful "composition" system on top of it. In the case of our shell, the router is tracking the current route. It stores the route's module instance in its activeItem observable property. The router is then bound through Durandal's compose binding (Knockout containerless comment syntax used here). Now any time the router changes its active item, the DOM will re-compose with the new view. Here's how it happens:

  1. A route is triggered and the router finds the module and sets it as its activeItem.
  2. The compose binding detects that the activeItem has changed. It examines the value and uses that to find the appropriate view (you guessed it...using the viewLocator).
  3. The activeItem and the located view are data-bound together.
  4. The bound view is inserted into the DOM at the location of the compose binding.
  5. If the compose binding specifies an animation, it is used to smoothly show the new view.

The compose binding is used here to enable navigation by "composing" in different views. It is a very versatile and powerful feature of the framework capable of doing much, much more than this. By combining the ability to break down an app into small modules, each with their own view, along with the ability to re-compose in the UI, you can accomplish extremely complex user experiences, with relatively little effort.

Note: It's important to note that the router must be activated with a default route before it can function properly. Router activation is asynchronous so the router’s activate promise is returned to Durandal through the shell’s activate function. You can learn more about the power of asynchronous activation and screen lifecycles in the documentation on the viewModel module.

Views and View Models

Each page in our navigation application is comprised of a view and a view model. Once you've set up the structure as described above, all there is to extending the application is dropping new view models in the viewmodels folder along with an appropriate view in the views folder. Then, you just register them with the router in main.js. When the corresponding route is navigated to, the router will locate your module and the composition infrastructure will see to it that it's bound and inserted into the DOM. Why don’t we add a simple page? Under the viewmodels folder, add a file called myPage.js with the following code:

define(function (require) {
    var app = require('durandal/app');

    return {
        displayName: 'My Page',
        showMessage: function () {
            app.showMessage('Hello there!');
        }
    };
});

Under the views folder, add a file called myPage.html with the following markup:

<div>
    <h2 data-bind="html: displayName"></h2>
    <button class="btn" data-bind="click: showMessage">Click Me</button>
</div>

Finally, go to the main.js module and add the following router configuration below the existing calls to mapNav:

router.mapNav('myPage');

Now, run the application (make sure your browser isn’t caching resources) and you should see a new navigation option called ‘MyPage’. Click on it and you will navigate to your new page. It’s that simple.

Summary

Please join our google group to ask questions and share your experiences with other developers. Most importantly, use Durandal to create something. We hope you deeply enjoy the experience.