Windows Store app with TypeScript and Knockout

In this tutorial we’re going to make a very simple Windows Store app that uses TypeScript. The app is going to use Knockout to handle user input to search patterns on http://colourlovers.com. I assume you have read my previous tutorial on how to set up TypeScript compilation in a Windows Store app. We’ll start with an empty project after adding TypeScript compilation.

Packages

First we’ll start by adding our dependencies through NuGet. We have a dependency on KnockoutJS, because this is what we need for our bindings. To make the request to colourlovers we are going to use jQuery. First install both by searching them in the NuGet explorer, or type the following two commands in the NuGet console.

Install-Package knockoutjs
Install-Package jQuery

To make our lives a lot easier we are also going to install a couple of type definitions. There’s an awesome project going on over at GitHub, DefinitelyTyped. This collection contains definitions for many JavaScript frameworks and libraries. If you are using any third party library in your project you should definitely have a look at that.

For our convenience these definitions are available through NuGet also.

Install-Package jquery.TypeScript.DefinitelyTyped
Install-Package knockout.TypeScript.DefinitelyTyped

There are two definitions which we need also. There’s are for the WinRT and WinJS libraries used in the project. Although a version of these files come with TypeScript, the version on DefinitelyTyped are far more up to date. If you install the definition for WinJS, WinRT is also installed.

Install-Package winjs.TypeScript.DefinitelyTyped

That’s all for the packages. A small last trick. You can search by tag when looking for NuGet packages in the Package Manager, by starting your search with “tag:”. To search for anything on TypeScript, just search for “tag:typescript”.

Default.ts

Packages alone won’t bring us very far. Start by deleting the old default.js file and adding a new default.ts file. Add the code below to the file:

/// <reference path="../scripts/typings/winjs/winjs.d.ts" />
/// <reference path="../scripts/typings/knockout/knockout.d.ts" />

module Default {
    "use strict";

    WinJS.Application.onactivated = (args: any) => {
        if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) {
            args.setPromise(
                WinJS.UI.processAll().then(
                    (rootElement) => {
                        // apply knockout binding …
                    })
                );
        }
    };

    WinJS.Application.start();
}

The first two lines, in comments tell TypeScript to use these definitions as references. We create a module named Default. This code is pretty much the same as its JavaScript counterpart, except for the then on line 10. The function inside is called when the processing of the UI is done. This will be the place where we’re going to apply the knockout binding and create a new instance of our view model.

Knockout ViewModel

In this example we only need one view model. To create this, start by adding a new TypeScript file to the scripts folder. Add the following code to the new file.

/// <reference path="../scripts/typings/knockout/knockout.d.ts" />

module ColourLovers {

    export class PatternsViewModel {

        constructor() {

        }
    }
};

This code will be the basic outline of the view model. It starts by adding a reference to knockout, it than defines the module with the PatternsViewModel.

The class needs a property to store the query to use when calling the ColourLovers api. Add this piece of code to line 6:

query: KnockoutObservable<string> = ko.observable<string>();

This will add the query variable to the class and uses the generic type KnockoutObservable of type string. It’s that initialized immediately using the generic function observable on ko from Knockout. The use of this generic give you a great advantage over the JavaScript version. In JavaScript there’s no one stopping you for putting a number in there. This might cause problems on some occasions. TypeScript will give you an error, and in Visual Studio you’ll get a compiler error preventing you from running the broken code.

We also need a function to call whenever this value changes. At the following code between lines 9 and 10:

private search = function (value?){          

};

This function will only be used inside the class, thus it can be made private. Note that the private indication is only enforced by TypeScript. Because this function will also be called from the constructor, but without the parameter, it is nullable.

Speaking of which… Let’s implement the constructor and have it call the search function. The code on line 3 comes with Knockout. Whenever an observable changes its value, the subscribe function is call and the new value is passed to as a parameter. In this case it’s the value to search for on ColourLovers. We’ll just pass it on the search function.

constructor() {
    this.query.subscribe((value) => {
        this.search(value);
    });
    this.search();
}

The last thing to do in the view model is making the call to ColourLovers. This will be done in the search function.

private search = function (value?) {
    $.getJSON("http://www.colourlovers.com/api/palettes?format=json&keywords=" + value).then(
        (result) => {
            this.result(result);
        });
};

The code for the search function is pretty straight forward. All it does is making an Ajax call and then it uses a promise (the jQuery version in this case) to handle the result. I’m using the lambda version of a function in this case. You could write the search function on line 1 as a lambda also, but I prefer “regular” function to be written as such and callback as lambdas as this keeps the code a bit more readable.

The result is placed in the result variable. We’ll have to define this also. It’s just one single line to add right below the definition of the query variable we defined earlier. For now, we make de observable array of type any so we can add anything to that.

result: KnockoutObservableArray<any> = ko.observableArray<any>();

To kick off the databinding with knockout you’ll have to call its applyBindings function. To do that, place the line below in default.ts inside the lambda.

ko.applyBindings(new ColourLovers.PatternsViewModel(), rootElement);

You should be able to run the code now, although nothing will be shown on screen. But, if you place a breakpoint inside the result of the search function you should be able to hit that and see the result.

The View

The view is build using two parts. The header, which contains a title and the searchbox. And the contents that contains a list. The connection between the HTML and Knockout is made though the data-bind attributes.

<div class="header">
    <h1>TypeScript HTML App</h1>
    <div>
        <input placeholder="search" data-bind="value:query" />
        <button>Search</button>
    </div>
</div>

<div class="patternsOuter">
    <ul class="patternsList" data-bind="foreach:result">
        <li class="patternsListItems">
            <div>
                <img data-bind="attr:{src:imageUrl, alt:title}" />
            </div>
            <div data-bind="text:title"></div>
        </li>
    </ul>
</div>

 

To get a little formatting use the following CSS:

body {
    display: -ms-grid;
    -ms-grid-rows: 150px 1fr;
    -ms-grid-columns: 1fr;   
}
.patternsListItems {
    list-style-type: none;
    padding-bottom: 24px;
    padding-right: 24px;
}
.patternsList {
    display: -ms-flexbox;
    flex-flow: wrap; 
}
.patternsOuter {    
     overflow-y: scroll;    
     -ms-grid-row: 2;
}
.header {
    margin-left: 40px;
    -ms-grid-column: 1;
}

The data-bind on line 4 of the html binds the query property to the value of the textbox. Because it is two-way whenever the value changes the code is updated and the subscribe method is called on the view model.

The second data-bind is a foreach on the result property which is filled with the results of the query when the ajax call to ColourLovers is done. Everything inside the HTML-tag the foreach handler is on will be repeated for every item. This creates a list of <li/> with an image and a title.

Try running the code again. The application should work as expected now.

WinJS through helper

What happens if you would like to use a WinJS control in combination with Knockout? For example, a rating control. The patterns returned by ColourLovers contain a numHearts property. This is a floating point value between 0 and 5, which is perfect to show in a rating control. The HTML for the control looks as follows:

<div class="ratingControl" data-win-control="WinJS.UI.Rating"
     data-win-options="{ maxRating: 5 }"
     data-bind="average:numHearts"></div>

On line 3 you can find the Knockout databinding. But, the average handler is not defined by Knockout. Because Knockout knows nothing about WinJS and its controls you’ll have to help it a little. I’ve added a TypeScript file to the solution, KoHandlers.ts and added following code to that:

ko.bindingHandlers["average"] =
    <KnockoutBindingHandler>{
    init: (element, valueAccessor, allBindings, viewModel, bindingContext) => {
        WinJS.UI.process(element);
    },
    update: (element, valueAccessor) => {
        var value = valueAccessor() || {};

        if (element.winControl) {
            element.winControl.averageRating = value;
        }
    }
};

Make sure you add the .js file the .html file.

When Knockout initializes the binding the init function is called. To make sure the control is processed this is where we call WinJS.UI.process passing it the control.

When the value in the view model changes the update function is called. All we do is grabbing the value from the accessor, checking if the control is still there.

This handler does not update the view model when the user selects another rating. To implement something like that you’ll have to add an event handler to the init function and monitor the changes yourself.

Wrap-up

I hope I’ve showed a bit how you could use Knockout in your TypeScript store apps. Now we’ve got the basics out of the way we can dive in a little deeper into using TypeScript in your Store apps next time. If you have any question or suggestions please leave them in the comments or send me tweet or email.

Demo application

Leave a Reply

%d bloggers like this: