Custom Navigation Bindings

Useful navigation bindings can be developed by using methods such as pager.getParentPage, and pager.Page.prototype.getFullRoute. On this page we'll develop one such binding: page-step:.

The goal of the binding is to be able to use it like

    <a data-bind="page-step: -1">Go back and read about {0}</a>
    <a data-bind="page-step: 1">Next page</a>

where page-step: -1 indicates that the link should point at the previous page and page-step: 1 indicates that the link should point at the next page. If you look at the buttons below you'll see that they use this binding!

The page-step: binding needs to

  1. Know which page it should link to. Thus we need a reference to the page. Getting a reference to the page that contains the a-tag can be done using pager.getParentPage. Navigating to the sibling pages can be done using pager.Page#children-observable array and pager.Page#parentPage. Using pager.Page.prototype.nullObject it is possible to make the reference robust for race conditions.
  2. Calculate the absolute path to the page and fetch the title of the page. This is easily done using pager.Page.prototype.getFullRoute and pager.Page.prototype.val using 'title' as argument.
  3. Bind the page link and page title.

We start by developing a method to find the sibling

// return the index of the current page
var indexOfCurrentPage = function(page) {
    return page.parentPage.children.indexOf(page);
};

// returns a sibling page, or the nullObject-page if no sibling exists at the moment
var pageStep = function(page, step) {
    return ko.computed(function() {
        var rawStep = ko.utils.unwrapObservable(step);
        return page.parentPage.children()[(indexOfCurrentPage(page)) + rawStep] || page.nullObject;
    });
};

that now can be used in a new class:

// new constructor
var NextPage = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    this.element = element;
    this.bindingContext = bindingContext;
    this.path = ko.observable();
    var parent = pager.getParentPage(bindingContext);
    this.siblingPage = pageStep(parent, valueAccessor());
};

Now we got a reference to the page (this.sublingPage). Next we need to calculate the absolute path to the page.

    // this line already exists
    this.siblingPage = pageStep(parent, valueAccessor());
    // calculate the path to the sibling page as a computed observable
    this.path = ko.computed(function () {
        var value = this.siblingPage();
        return value.getFullRoute()().join('/');
    }, this);
};

Let us bind the path and title to the element.

NextPage.prototype.bind = function () {
    // Here we bind the text inside the a-tag
    var text = $(this.element).text();
    ko.computed(function () {
        var page = this.siblingPage();
        if (text.length === 0) {
            // Use the page title as text if no text is supplied inside the a-tag
            var newText = page.val('title');
            ko.applyBindingsToNode(this.element, {
                'text':newText
            });
        } else if (text.indexOf('{0}')) {
            // Search replace {0} with the page title
            var replacedText = text.replace('{0}', page.val('title'));
            ko.applyBindingsToNode(this.element, {
                'text':replacedText
            });
        }
    }, this);

    // Here we bind the path to the sibling page
    var hash = ko.computed(function () {
        return pager.Href.hash + this.path();
    }, this);
    ko.applyBindingsToNode(this.element, {
        attr:{
            'href':hash
        }
    });
};

Finally we register the binding using KnockoutJS.

// register the binding as page-step
ko.bindingHandlers['page-step'] = {
    init:function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        // call the constructor in order to setup element, bindingContext and and siblingPage
        var nextPage = new NextPage(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
        // bind href and text
        nextPage.bind();
    }
};

If you inspect the source code of the navigation buttons you'll see that this binding is used for their navigation.

Back to {0} Read about Crawling