Language

DurandalJS template

By Mads Kristensen|

The DurandalJS MVC Template is written by Rob Eisenberg

Download the Durandal MVC Template

Durandal is small JavaScript framework designed to make building Single Page Applications (SPAs) simple and elegant. We didn't try to re-invent the wheel. Durandal is built on libs you know and love like jQuery, Knockout and RequireJS.

There's little to learn and building apps feels comfortable and familiar.

Architecture

Durandal has strong support for MVC, MVP and MVVM. No matter what front end architecture paradigm you prefer, Durandal is there to back you up. With RequireJS as our base and a thin layer of conventions, we can provide amazing productivity while helping you to maintain SOLID coding practices. Pair that with out-of-the-box support for rich UI composition, modal dialogs, eventing/messaging, widgets, transitions, routing and more....and there's no doubt you'll be able to build whatever apps you can imagine; the apps of today and of tomorrow.

Features

  • Clean MV* Architecture
  • JS & HTML Modularity
  • Simple App Lifecycle
  • Eventing, Modals, Message Boxes, etc.
  • Navigation & Screen State Management
  • Consistent Async Programming w/ Promises
  • App Bundling and Optimization
  • Use any Backend Technology
  • Built on top of jQuery, Knockout & RequireJS
  • Integrates with other libraries such as SammyJS & Bootstrap
  • Make jQuery & Bootstrap widgets templatable and bindable (or build your own widgets)

Platform Support

Building an Android phone app? An enterprise LOB targeted at Windows? A web gaming platform? No matter how large or small the app, it's effortless with Durandal....and we give you the tools to develop on any platform, for any platform. Use it in the browser for web deploy, with PhoneGap for phone and tablet deploy or with AppJS for PC/Mac/Linux desktop deploy. One code base; many platforms.

Want to use a .NET backend? A NodeJS Express server? Call 3rd party web APIs? Durandal works with any backend. Use our http module, jQuery's ajax helpers or any 3rd party data layer you please, such as BreezeJS.

Understanding the Template

The Durandal SPA Template 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. To get started, download the VSIX and install it by double clicking it. Next, open Visual Studio and from the Start Page or File menu, select New Project.... When the New Project dialog appears, choose ASP.NET MVC 4 Web Application. In the following dialog, you will be able to select your template. Choose Durandal SPA Project.

Organization

After Visual Studio creates your project, run it to see the basic navigation app experience. Then, go back to Visual Studio and expand the App folder and you will find the source for the entire SPA application. Here's the high level organization you will find:

  • App
  • durandal/
  • viewmodels/
  • views/
  • main.js

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.cshtml.

Index.cshtml

The Index.cshtml (located at Views/Durandal/Index.cshtml) 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 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

  • Durandal apps are organized into AMD modules and HTML views.
  • Developers use main.js to configure the framework, set up 3rd party libraries and start Durandal with their "root" view model (or view).
  • You can use the router plugin to create a navigation-style application by requiring it and configuring it with routes. (But you don't have to.)
  • Your shell activates the router (if present) and sets up its basic data and functionality.
  • Use "composition" in your shell's view to realize page changes or to generally construct complex screens throughout your app.
  • Extend the application by creating views and view models for each page and optionally for sub-components of each page.
  • Check out the documentation to learn more about other features such as widgets, modals and messaging.

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.

This article was originally created on January 30, 2013

Author Information

Mads Kristensen

Mads Kristensen – Program Manager at the Web Platform & Tools team, author of Web Essentials and founder of BlogEngine.NET. You can always find Mads at madskristensen.net or on twitter at @mkristensen