1

I am new to durandal and single page apps, I am having issues getting the deactivate and canDeactivate function to fire. I am using some custom code to achieve deep linking, which is probably what is causing my issue. I followed the example here: https://github.com/evanlarsen/DurandalDeepLinkingExample please also see Durandal Subrouting (Hottowel)

Any help would be most appreciated.

Here is the viewmodel code I am calling:

define([''], function () {

var vm = {
    activate: function () {
        alert('In activate!');
    },
    deactivate: function () {
        alert('In deactivate!');
    },
    canDeactivate: function () {
        alert('In candeactivate!');
    }
}
return vm;

});

Here is the viewhtml

<div class="container-fixed"> 
 <div>
    <header>
        <!-- ko compose: {view: 'Main/Users/UsersNav'} -->
        <!-- /ko-->
    </header>
    <section id="content" class="in-users">
        <!--ko compose: { 
                            model: inUsers, afterCompose: router.afterCompose,
                            transition: 'entrance', 
                            activate: true
                        } -->
        <!--/ko-->
    </section> 
 </div>
</div>

Here is the calling code:

define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
    var App = {
        router: router,
        activate: activate,
        showPage: showPage,
        isPageActive: isPageActive,
        inUsers: viewModel.activator(),
    };
    return App;

    var defaultPage = '';
    function activate(activationData) {

        defaultPage = 'ManageUsers';
        App.inUsers(convertSplatToModuleId(activationData.splat));

        router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
            if (currentItem != newItem) {
                return false;
            }
            else {
                App.inUsers(convertSplatToModuleId(data.splat));
                return true;
            }
        };
    }

    function showPage(name) {
        return function () {
            router.activate('#/Users/' + name);
            //router.navigateTo('#/Users/' + name);
            App.inUsers(convertNameToModuleId(name));
        };
    }

    function isPageActive(name) {
        var moduleName = convertNameToModuleId(name);
        return ko.computed(function () {
            return App.inUsers() === moduleName;
        });
    }

    // Internal methods
    function convertNameToModuleId(name) {
        return 'Main/Users/' + name + '/' + name;
    }

    function convertSplatToModuleId(splat) {
        if (splat && splat.length > 0) {
            return convertNameToModuleId(splat[0]);
        }
        return convertNameToModuleId(defaultPage);
    }
});

EDIT: (Main master page)

 function activate() {

        // my convention
        router.autoConvertRouteToModuleId = function (url) {
            return 'Main/' + url + '/index';
        };

        return router.activate('Home'); 
    }

Nav HTML for master:
<div class="btn-group">
                     <a href="#/home" class="btn btn-info">HOME</a>
                     <a href="#/resources" class="btn btn-info">RESOURCES</a>  
                     <a href="#/users" class="btn btn-info">USERS</a>               
 </div>    


Main master: 
  <div class="container-fixed"> 
   <div>
    <header>
        <!-- ko compose: {view: 'Main/masterNav'} -->
        <!-- /ko-->
    </header>
    <section id="content" class="main">
        <!--ko compose: {model: router.activeItem, 
            afterCompose: router.afterCompose,
            transition: 'entrance'} -->
        <!--/ko-->
    </section>        
    <footer>
        <!--ko compose: {view: 'Main/masterFooter'} --><!--/ko-->
    </footer>
  </div>
 </div>   
Community
  • 1
  • 1
Mark Beattie
  • 175
  • 1
  • 13

1 Answers1

2

The issue you are running into about not being able to deactivate your sub-routed views is because the viewmodel.activator() observable, that is returned from that method, enforces the activator pattern in durandal. That observable is expecting a amd module and not a string.

Even though the string works fine because the compose binding knows how to load modules based off of the string.. the viewmodel activator doesn't know how to load modules from strings.

So, you need to pass the actually module to the observable.

The example I created before just used a string so it will compose the view.. but the activator pattern doesnt work if there is just a string. So, instead you will have to require all your sub-route amd modules into your calling code and then instead of using the convertSplatToModuleId method.. create a new method that returns the correct module.

So, something like this:

define(['durandal/system', 'durandal/viewModel', 'durandal/plugins/router'],
function (system, viewModel, router) {
    var App = {
        router: router,
        activate: activate,
        showPage: showPage,
        isPageActive: isPageActive,
        inUsers: viewModel.activator(),
    };
    return App;

    var defaultPage = '';
    function activate(activationData) {

        defaultPage = 'ManageUsers';
        convertSplatToModuleId(activationData.splat).then(function(activeModule){
            App.inUsers(activeModule);
        })


        router.activeItem.settings.areSameItem = function (currentItem, newItem, data) {
            if (currentItem != newItem) {
                return false;
            }
            else {
                convertSplatToModuleId(data.splat).then(function (module) {
                    App.inUsers(module);
                });
                return true;
            }
        };
    }

    function showPage(name) {
        return function () {
            router.activate('#/Users/' + name);
            //router.navigateTo('#/Users/' + name);
            convertNameToModuleId(name).then(function(module){
                App.inUsers(module);
            });            
        };
    }

    // Internal methods
    function convertNameToModuleId(name) {
        return system.acquire('Main/Users/' + name + '/' + name);
    }

    function convertSplatToModuleId(splat) {
        if (splat && splat.length > 0) {
            return convertNameToModuleId(splat[0]);
        }
        return convertNameToModuleId(defaultPage);
    }
});
Evan Larsen
  • 9,935
  • 4
  • 46
  • 60
  • Related to this question, I now have an issue when I click on my main (non subrouted) pages, when I click on these I don't get the candeactivate and deactivate methods called. Please see above for code – Mark Beattie Jul 16 '13 at 09:56
  • It would be better to ask that as a new question. – RainerAtSpirit Jul 16 '13 at 10:52
  • It is tied in with this question, I am hoping Evan will know. – Mark Beattie Jul 16 '13 at 11:47
  • check to see if the observable that is bound to the durandal compose binding handler is a amd module and not a string. – Evan Larsen Jul 17 '13 at 04:50
  • Hi Evan thanks for getting back to me, the edit section above contains all the relevant details, please correct me if I am wrong. The setup is basically the same as the project you put together. It is simply displaying the activeItem I believe, I'm not entirely sure how to correct though. // my convention router.autoConvertRouteToModuleId = function (url) { return 'Main/' + url + '/index'; }; return router.activate('Home'); – Mark Beattie Jul 17 '13 at 09:39
  • I'm not sure. Can you zip up your solution and drop it somewhere? – Evan Larsen Jul 18 '13 at 16:44
  • Hi Evan, I have sent you an email. Please check your junk folder. – Mark Beattie Jul 19 '13 at 08:14