WInJS Navigation and KnockoutJS, expanding the to-do sample

I was planning to expand my to-do sample with some more WinJS controls to weed out any bugs that might still be present in the knockout-winjs integration library. After thinking of some controls I would like to test out I figured I would need more than just the single page currently in the sample app. And that of course means some way to navigate between them.

WinJS comes with a navigation stack built-in, and it would be ridiculous not to use that. However a “simple” sample I found online (this one) turned out less simple than I expected. After some digging through the code however I found that for my simple purpose I could strip most of the plumbing and get something actually simple. So first we will set up some plumbing and then add a to-do item edit page.

I’m building on the outcome of my last blog post: ToDoApp2

Setting up the navigation plumbing

A few changes to the way we initialize the application:

function onDeviceReady() {
  // Handle the Cordova pause and resume events
  document.addEventListener('pause', onPause, false);
  document.addEventListener('resume', onResume, false);

  ConcreteCoding.KnockoutToWinJS.addBindings(ConcreteCoding.KnockoutToWinJS.controls);

  // When we navigate to a page, this event listener will render it, and apply our knockoutjs bindings.
  WinJS.Navigation.addEventListener("navigated", (eventObject) => {
    var url = eventObject.detail.location;
    var host = document.getElementById("pageContent");

    // Unload the previous page if needed.
    if (host.winControl && host.winControl.unload) {
      host.winControl.unload();
    }
    WinJS.Utilities.empty(host);

    eventObject.detail.setPromise(WinJS.UI.Pages.render(url, host, eventObject.detail.state).then(function () {
      ko.applyBindings(eventObject.detail.state, host);
    }));
  });

  WinJS.UI.processAll().then(() => {
    // Navigate to the initial page and supply the original view model as the state parameter.
    return WinJS.Navigation.navigate("/html/ToDoView.html", new ToDoViewModel());
  });            
}

And we also have to extract the page from the index.html into its own ToDoView.html.

Index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>ToDoApp</title>

    <link href="WinJS/css/ui-light.css" rel="stylesheet" />
    <script src="WinJS/js/WinJS.js"></script>

    <link href="css/index.css" rel="stylesheet" />

    <script src="scripts/platformOverrides.js"></script>
    <script src="scripts/knockout-3.3.0.debug.js"></script>
    <script src="scripts/knockout-winjs.js"></script>
    <script src="scripts/index.js"></script>

    <script src="cordova.js"></script>    
</head>
<body>
    <div id="pageContent"></div>
</body>
</html>

ToDoView.html:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <input type="text" data-bind="textInput: newDescription" />
    <br />
    <div data-bind="foreach: toDoItems">
        <input type="checkbox" data-bind="checked: done" />
        <span data-bind="text: description"></span>
        <a href="#" data-bind="click: $parent.removeItem.bind($parent, $data)">x</a>
        <br />
    </div>

    <div data-bind="winAppBar: {placement: 'bottom'}">
        <button data-bind="winAppBarCommand: {label: 'Add', type: 'button', icon:'add', onclick: addNewItem }"></button>
    </div>    
</body>
</html>

Adding the new page with the navigation button

So ofcourse we create a new file containing the HTML for the new page. Very simple but using the WinJS BackButton control, because we are using the WinJS navigation stack. I also changed some properties in the ToDoItem class so we have a bit more to fill the page with.

The new HTML page:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <button data-bind="winBackButton"></button>
    <h1 class="win-type-xx-large">Edit item</h1>
    <br />
    <label>Title</label>
    <br />
    <input type="text" data-bind="textInput: toDoItem().title" />
    <br />
    <label>Done?</label>
    <input type="checkbox" data-bind="checked: toDoItem().done" />
    <br />
    <label>Description</label>
    <br />
    <textarea data-bind="textInput: toDoItem().description"></textarea>
</body>
</html>

The new view model class for the above view, and the new ToDoItem class:

class EditToDoItemViewModel {
    toDoItem: KnockoutObservable<ToDoItem>;

    constructor(item: ToDoItem) {
        this.toDoItem = ko.observable(item);
    }
}

class ToDoItem {
    title: KnockoutObservable<string>;
    description: KnockoutObservable<string>;
    done: KnockoutObservable<boolean>;

    constructor(title: string) {
        this.title = ko.observable(title);
        this.description = ko.observable("");
        this.done = ko.observable(false);
    }
}

Finally we add an editItem method that handles navigating to our new page, we place it in the ToDoViewModel class. This method should also be data bound on a button or link for each to-do item in the main page, but I’ll leave that as an exercise for the reader (or you can just download the complete project at the end of the post):

editItem(item: ToDoItem) {
    WinJS.Navigation.navigate("/html/EditToDoItemView.html", new EditToDoItemViewModel(item));
}

Now just run it, and you should be able to add items, edit them in a separate edit page, and navigate back to the original page. The complete project can be found here: ToDoApp3

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.