Exploring TypeScript: TODO MVC Sample

Tags: TypeScript

This entry continues exploring TypeScript by examining the code from one of the samples.  Today, I’ll look at the TypeScript version of the MVC TODO sample. This sample is a TypeScript version of an MVC / JavaScript sample.

The samples are now getting longer, and have more features in them. I’ve also posted some samples and explained some of the basic syntax. From here on out, I’m only going to extract small bits of the code, and explain the features that I haven’t yet explained. As always, the samples are online both as a working example and as code.

This sample has a simple TODOD checked list.  You can see it running here. You can add new tasks. and mark any of those tasks as complete. The source code for the TypeScript version is here. That source code also contains a link to the original JavaScript version on Github, if you want to compare the JavaScript and TypeScript versions.

Let’s look at what’s new in this sample.

The HTML file, index.html, demonstrates a few new features (at least for this series). It includes a number of common JavaScript libraries. This app also uses with Json2, Underscore, and Backbone. 

Toward the bottom of the page, there are two template scripts that are used by the Underscore library.

<!-- Templates –>
<script type="text/template" id="item-template">
    <div class="todo <%= done ? 'done' : '' %>">
        <div class="display">
            <input class="check" type="checkbox" <%= done ? 'checked="checked"' : '' %> />
            <label class="todo-content"><%= content %></label>
            <span class="todo-destroy"></span>
        </div>
        <div class="edit">
            <input class="todo-input" type="text" value="<%= content %>" />
        </div>
    </div>
</script>

<script type="text/template" id="stats-template">
    <% if (total) { %>
        <span class="todo-count">
            <span class="number"><%= remaining %></span>
            <span class="word"><%= remaining == 1 ? 'item' : 'items' %></span> left.
        </span>
    <% } %>
    <% if (done) { %>
        <span class="todo-clear">
            <a href="#">
                Clear <span class="number-done"><%= done %></span>
                completed <span class="word-done"><%= done == 1 ? 'item' : 'items' %></span>
            </a>
        </span>
    <% } %>
</script>

The script type is “text/template”, so the browser will not try to parse and understand these two scripts.  But, they do have valid id’s and can be referenced as HTML snippets by the other script code in this sample.

Let’s move on to the todo.ts file.

As in the previous sample, the start of the file declares type information for the types referenced from Backbone, Underscore, and JQuery. Unlike the previous sample, this sample declares those types inline.

Again, I’ll make my plug for DefinitelyTyped. All three of these projects are already part of that project. 

There are a few new bits of syntax in these interface definitions that deserve explanation.

Here’s the definition of the Model class exported from Backbone. Note the highlighted portions.

The any type and optional parameters and properties

export class Model {

    constructor (attr? , opts? );
    get(name: string): any;
    set(name: string, val: any): void;
    set(obj: any): void;
    save(attr? , opts? ): void;
    destroy(): void;
    bind(ev: string, f: Function, ctx?: any): void;
    toJSON(): any;
}

I’ve highlighted the ‘any’ keyword in green. Any variable of type ‘any’ corresponds to a value in JavaScript. Minimal static type checking is performed on a variable of type any.

The declarations highlighted in yellow are optional properties or parameters. These may be omitted from the calls.

Generics

The Backbone type definitions also provide the first example we’ve seen of Generics, the Collection<T>:

export class Collection<T> {
    constructor (models? , opts? );
    bind(ev: string, f: Function, ctx?: any): void;
    length: number;
    create(attrs, opts? ): any;
    each(f: (elem: T) => void ): void;
    fetch(opts?: any): void;
    last(): T;
    last(n: number): T[];
    filter(f: (elem: T) => boolean): T[];
    without(...values: T[]): T[];
}

The syntax for the generic collection (highlighted in yellow) should be familiar to most of my readers. TypeScript generics provide type safety while using TypeScript, but compile down to a typical JavaScript implementation. I’m not devoting much space to generics, because they should look very familiar to you. That might give you the impression that generics aren’t that important. Don’t let that mislead you:  Generics in TypeScript are *awesome*.

The other features that I highlighted above (in green) is the ellipsis before the parameter on the last method. That indicates a variadic  parameter. That’s the same as a ‘params’ array in C#, or other languages you may be familiar with. It means that the parameter list will be placed in an array, and that array is passed to the without method.

There’s one final new syntax element above that I’ll mention.  The ‘each’ method has a parameter that is a function. The signature for that function uses the arrow syntax popularized in C#’s lambda expression. And yes, I know I didn’t highlight it, but I was running out of nice highlight colors.

The implementation

The application starts running in response to the JQuery ‘ready’ event. Most of the code is plain old JavaScript, and there are enough comments to explain how the JavaScript portions work.

One TypeScript idiom deserves mention.  The TodoView re-declares its model property so that the model is type safe.

The Backbone View type contains a model property (of type Backbone.Model):

declare module Backbone {
   
export class Model {
        // . . .
    }

    export class View {
        // . . .
        model: Model;
    }
}

Later, when the Todo model and view are declared, the model property of the view is re-defined:

class Todo extends Backbone.Model {
    // . . .
}

class TodoView extends Backbone.View {
    // . . .
    model: Todo;
    // . . .
}

 

Now, other code inside the TodoView that references the model property will see the model property as a Todo object, not as a Backbone.Model object. Unlike in other Object Oriented languages, it does not declare new storage. Code accesses the same storage whether it’s accessed through TodoView.model, or Backbone.View.model. The only difference is that in TypeScript, the available members will match the declared type.

Add a Comment