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