Recently, I played with the Hot Towel MVC template and tried to integrate Bootstrap's scrollspy. This was very difficult for me as my main topic is rather C# and WPF than JavaScript and Html. But after a struggle here and there, I found a solution. Not perfect (any improvements are welcome), but it's sufficient for me.
In shell.js, in the activate-method, we register the normal route ('alpha'), and for each anchor-link within the alpha-page, another route that maps to the same page ('beta' and 'gamma'). Now, when you click on an anchor on page 'alpha', the activate-method of the corresponding viewmodel will be called with the anchor-value as parameter (see below).
Nothing special for the html-part. The anchor-links are just normal relative anchor-links and the classes are mainly from bootstrap for the scrollspy- and affix-plugin.
The activate-function is called whenever a route to this viewmodel is activated. When the normal alpha-route is activated, it is called with "#/alpha" as hash and nothing happens. When an anchor-route is activated, it is called with "#/beta" for example and we scroll to this anchor with jQuery.
The viewAttached-function is called when the databinding has finished. Clicking an anchor-link calls this method not for the first time and nothing happens. The important work is done on the first call: the height of the navigation-div is fitted to its content, the affix-parameter is set and the scrollspy is activated (twice, because I possibly do something wrong here *g*).
The problem is that Sammy.js, which Durandal uses for routing, does the routing via hashes (#), which apparently are also used for anchor-navigation. In order to prevent Sammy.js to incorrectly interpret the anchor-links, I added an invisible route for them to the viewmodel of the current page and route it there manually. Hard to describe, see the code!
//shell.js:
router.mapNav('alpha', 'viewmodels/alpha', 'Alpha');
router.mapRoute('beta', 'viewmodels/alpha', 'Alpha', false);
router.mapRoute('gamma', 'viewmodels/alpha', 'Alpha', false);
In shell.js, in the activate-method, we register the normal route ('alpha'), and for each anchor-link within the alpha-page, another route that maps to the same page ('beta' and 'gamma'). Now, when you click on an anchor on page 'alpha', the activate-method of the corresponding viewmodel will be called with the anchor-value as parameter (see below).
//alpha.html:
Nothing special for the html-part. The anchor-links are just normal relative anchor-links and the classes are mainly from bootstrap for the scrollspy- and affix-plugin.
//alpha.js:
define(['durandal/plugins/router'], function (router) {
var vm = {
activate: activate,
viewAttached: viewAttached,
loaded: false,
items: [
{ name: "Beta", link: "beta" },
{ name: "Gamma", link: "gamma" },
]
};
return vm;
function activate(routeParameters) {
if (routeParameters.routeInfo.hash != "#/alpha") {
$(document.body).animate({
'scrollTop': $(routeParameters.routeInfo.hash.replace("/", "")).offset().top
}, 100);
return false;
}
return true;
}
function viewAttached() {
if (!vm.loaded) {
vm.loaded = true;
$('.bs-docs-sidebar').height($(".bs-docs-sidenav").height());
$('.bs-docs-sidenav').affix({
offset: $('#nav').position()
});
$('.bs-docs-sidebar').scrollspy();
$('[data-spy="scroll"]').each(function() {
var $spy = $(this).scrollspy('refresh');
});
}
}
The activate-function is called whenever a route to this viewmodel is activated. When the normal alpha-route is activated, it is called with "#/alpha" as hash and nothing happens. When an anchor-route is activated, it is called with "#/beta" for example and we scroll to this anchor with jQuery.
The viewAttached-function is called when the databinding has finished. Clicking an anchor-link calls this method not for the first time and nothing happens. The important work is done on the first call: the height of the navigation-div is fitted to its content, the affix-parameter is set and the scrollspy is activated (twice, because I possibly do something wrong here *g*).
//app.css:
.affix {
position: fixed;
}
// index.cshtml: < body data-spy="scroll" data-target=".bs-docs-sidebar">