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