5

I'm having a look at the Hot Towel template, and trying to get it to work in TypeScript, I've encountered a problem converting the shell viewmodel. I'm trying to translate this into TS, and it makes more sense to me that it should be a class, rather than simply exporting functions as shown here. I looked at this approach but, paying attention to the comments here, decided against following it.

After a bit of digging I found this thread, which suggested it should be as simple as overriding router.getActivatableInstance, but I can't seem to get far enough for that function ever to be called.

Here's my main.ts (also wrapped up in a class):

/// <reference path="dts/toastr.d.ts" />
/// <reference path="dts/durandal.d.ts" />
/// <reference path="dts/require.d.ts" />

import _app = module('durandal/app');
import _system = module('durandal/system');
import _viewLocator = module('durandal/viewLocator');
import _router = module('durandal/plugins/router');
import _logger = module('services/logger');

export class HOPS {

    public init(): void {
        _logger.log('init', this, 'HOPS', false);
        _system.debug(true);
        this._startApp();
    }

    private _startApp(): void {
        _logger.log('_startApp', this, 'HOPS', false);

        _app.start().then(() => {
            toastr.options.positionClass = 'toast-bottom-right';
            toastr.options.backgroundpositionClass = 'toast-bottom-right';

            var defImpl = _router.getActivatableInstance; //default Implementation

            _router.getActivatableInstance = function (routeInfo: _router.routeInfo, paramaters: any, module: any) {
                var functionName = routeInfo.name.substring(0, 1).toUpperCase() + routeInfo.name.substring(1);
                if (typeof module [functionName] == 'function') {
                    var instance = new module [functionName]();
                    instance.__moduleId__ = module.__moduleId__;
                    return instance;
                }
                else return defImpl(routeInfo, paramaters, module );
            }

            _router.handleInvalidRoute = (route: _router.routeInfo, paramaters: any) => {
                _logger.logError('No Route Found', route, 'main', true);
            }

            _router.useConvention();
            _viewLocator.useConvention();

            _app.adaptToDevice();

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

var hops: HOPS = new HOPS();
hops.init();

(Playground showing JS here: http://bit.ly/WyNbhn)

... and here's my shell.ts:

import _system = module('durandal/system');
import _router = module('durandal/plugins/router');
import _logger = module('services/logger');

export class Shell{

    public router = _router;

    public activate(): JQueryPromise {
        _logger.log("activate", null, 'shell', false);
        return this.boot();
    }

    public boot(): JQueryPromise {
        _logger.log("boot", null, 'shell', false);
        this.router.mapNav('home')
        this.router.mapNav('details');
        _logger.log('SciHops SPA Loaded!', null, _system.getModuleId(this), true);
        return this.router.activate('home');
    }
}

... which compiles to:

define(["require", "exports", 'durandal/system', 'durandal/plugins/router', 'services/logger'], function(require, exports, ___system__, ___router__, ___logger__) {
    /// <reference path="../dts/_references.ts" />
    var _system = ___system__;

    var _router = ___router__;

    var _logger = ___logger__;

    var shell = (function () {
        function shell() {

            this.router = _router;
        }
        shell.prototype.activate = function () {
            _logger.log("activate", null, 'shell', false);
            return this.boot();
        };
        shell.prototype.boot = function () {
            _logger.log("boot", null, 'shell', false);
            this.router.mapNav('home');
            this.router.mapNav('details');
            _logger.log('SciHops SPA Loaded!', null, _system.getModuleId(this), true);
            return this.router.activate('home');
        };
        return shell;
    })();
    exports.shell = shell;    
})

With the class above, I get a binding error because router is undefined. If I add export var router = _router then this bug goes away but the activate method on my shell class is never called.

All of the above works well for subsequent viewmodels, but just falls over on the shell.

What am I doing wrong, and how can I get a TS class working as my shell Durandal viewmodel?

Community
  • 1
  • 1
Jude Fisher
  • 11,138
  • 7
  • 48
  • 91
  • 1
    Why do you prefer to export class' instead of functions? In typescript I see class' as a way to get inheritance. Do you need to have your shell and main files as class'? What benefit are you accomplishing by making them class'? – Evan Larsen Mar 31 '13 at 23:17
  • Precisely for inheritance. I tend to build similar apps for a number of different clients (or sometimes several related apps for one). The shells of these apps are likely to have common features - I'd like to put those into a base class and override/extend as required. It's not as important as having the other viewmodels as classes (where already in a simple test app I have a base vm class doing common work), but it would be nice to have. – Jude Fisher Apr 01 '13 at 08:37
  • gotchya, i'll look into it soon, i'm quite busy lately but I'll try and look into this tomorrow if no one else answers it. – Evan Larsen Apr 01 '13 at 23:58
  • Evan - Many thanks, and no rush - it works perfectly well as functions of course. – Jude Fisher Apr 03 '13 at 00:58

2 Answers2

4

The problem is your router is not resolving your shell.ts module.

All your other viewmodels are passing through the router.getActivatableInstance method and are being instantiated and having __moduleId__ affixed to them. Except your shell.ts module.

This is a quick and dirty way to get your shell.ts module to work.

Inside your main.ts you can require your shell.ts module:

import _shell = module('shell');

Then, still inside your main.ts you can instantiate your shell class and assign it the moduleId.

var shell = new _shell.Shell();
shell.__moduleId__ = _shell.__moduleId__;
_app.setRoot(shell, 'entrance');

Not the prettiest thing you've ever seen. But it gets the job done. Thats pretty much doing the same thing as the getActivatableInstance override your doing.. Since, your shell is not passing through the router.js module, you have to do it manually.

Evan Larsen
  • 9,935
  • 4
  • 46
  • 60
4

Another option is to export the class as the module for Duranal. note: this method also works for viewmodels.

A working example based on nuget's "Durandal Starter Kit":

    import router = require('plugins/router')
    import app = require('durandal/app')

    class Shell {
        router =  router;
        public search() {
            //It's really easy to show a message box.
            //You can add custom options too. Also, it returns a promise for the user's response.
            app.showMessage('Search not yet implemented...');
        }
        public activate() {
            router.map([
                { route: '', title: 'Welcome', moduleId: 'viewmodels/welcome', nav: true },
                { route: 'flickr', moduleId: 'viewmodels/flickr', nav: true },
            ]).buildNavigationModel();


            return router.activate();
        }
    }

    // Magic happens here:
    export = Shell;

The original 'js' code:

    define(['plugins/router', 'durandal/app'], function (router, app) {
        return {
            router: router,
            search: function() {
                //It's really easy to show a message box.
                //You can add custom options too. Also, it returns a promise for the user's response.
                app.showMessage('Search not yet implemented...');
            },
            activate: function () {
                router.map([
                    { route: '', title:'Welcome', moduleId: 'viewmodels/welcome', nav: true },
                    { route: 'flickr', moduleId: 'viewmodels/flickr', nav: true },
                ]).buildNavigationModel();


                return router.activate();
            }
        };
    });
Kedem
  • 274
  • 2
  • 5