モノトーンの伝説日記

Apex Legends, Splatoon, Programming, and so on...

Visual Studio と TypeScript、Knockout.js、Linq.js を使って、MVVM してみよう!

 なんかタイトルが長いですが、Web 開発も Visaul Studio でやろう! みたいなノリで思ってもえらばいいんじゃないかな。

概要

  1. やろうと思ったキッカケ
  2. タイトルについて解説
  3. 実際に使ってみよう! Visual Studio
  4. 実際に MVVM してみよう!
  5. サンプル
  6. まとめ

1. やろうと思ったキッカケ

 もともと TypeScript に興味があり、また Web で MVVM な開発をやりたかった、ってところが本音。そんなこんなで、最終的には HTML5JavaScriptCSS になるのだけど、その前の事前段階に HTML5、TypeScript with Knockout.js/Linq.js、Less を使おうというもの。

2. タイトルについて解説

2.1. TypeScript

 JavaScript が型を持ったもの、って思えばいいんじゃないのかな。ActionScript 書いたことある人なら、まああの感じに近い。しいて言えば、JavaScript と違って型を厳密に評価するので、JavaScript ライブラリーは逆に型宣言された TypeScript 用宣言が必要になる。

2.2. Knockout.js

 JavaScript で Binding ができるというやつ。xaml 触ったことある人ならなじみやすいと思う。

2.3. Linq.js

 ループ書くと 4 行ぐらいになってだるいやつを 1 行で済ませたり。メソッドチェーンで書けるので流れは読みやすい。インテリセンスが効く TypeScript 化ではすいすいかける。

3. 実際に使ってみよう! Visual Studio

 なお、この解説は Visual Studio 2013 Professional Update 2 RC をもとに作られています。

  1. 起動して、「新しいプロジェクト…」を選びます。
  2. 「インストール済み」「テンプレート」「TypeScript」から「TypeScript を使用した HTML アプリケーション」を選びます。
  3. 名前(N): に “FirstTimeMvvmTestApp” とでも入れておきましょう。

これでプロジェクトの作成は完了しました。次に Knockout.js などもろもろのものを入れます。

  1. 「ソリューション エクスプローラー」から “FirstTimeMvvmTestApp” を右クリック、「NuGet パッケージの管理…」を開きます。
  2. 「オンライン」「nuget.org」を選択し、右上の “オンライン の検索 (Ctrl+E)” に knockoutjs といれ、knockoutjs を選択し「インストール」します。

    f:id:mntone:20140504212803p:plain

  3. 同様に knockout.TypeScript といれ、knockout.TypeScript.DefinitelyTyped を選択し「インストール」します。

    f:id:mntone:20140504212952p:plain

  4. 今回は linq.js も使うので同様にいれます。

    f:id:mntone:20140504213231p:plain

これで準備完了です。

4. 実際に MVVM してみよう!

 今回はサーバーから JSON として受け取ったデータ (Model によって処理済み) を表示、編集することにします。次のような状況を想定します:

DataClient クラスから User[] データを受け取り、それをテーブルとして表示。必要に応じてユーザーを選択、編集し即時リストに反映。そしてデータをストア。

 簡単のため、実際に通信は行いません。それでは行きましょう。

4.1. User.ts

 ユーザーを格納するためのクラス。

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

class User
{
name: KnockoutObservable<string> = ko.observable( "" );
age: KnockoutObservable<number> = ko.observable( 0 );

constructor( name: string, age: number )
{
this.name( name );
this.age( age );
}

toString()
{
return this.name() + " (" + this.age() + ")";
}
}

簡単ですね! C# で書くとこんな感じ!

namespace Sample
{
public class User: NotificationObject
{
private string _name;
private uint _age;

User( string name, uint age )
{
this.Name = name;
this.Age = age;
}

property string Name
{
get { return this._name; }
set { this.SetValue( ref this._name, value ); }
}

property uint Age
{
get { return this._age; }
set { this.SetValue( ref this._age, value ); }
}
}
}

※ NotificationObject は通知関連の機能を実装済みとする。

4.2. DataClient.ts

 JSON.parse、JSON.stringify を使った簡単実装。

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

class DataClient
{
static data: string = '[{"name":"太郎", "age": 12}, {"name":"花子", "age": 11}]';

static load()
{
var json = JSON.parse( this.data );
return Enumerable.from( json ).select( ( user: any ) => new User( user.name, user.age ) ).toArray();
}

static store( value: User[] )
{
this.data = JSON.stringify( Enumerable.from( value ).select( user => { return { name: user.name, age: user.age }; }).toArray() );
alert( "stored: " + this.data );
}
}

4.3. IndexPageViewModel

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

class IndexPageViewModel
{
users: KnockoutObservableArray<User> = ko.observableArray( [] );
selectedUser: KnockoutObservable<User> = ko.observable( null );

avarageAge: KnockoutComputed<number> = ko.computed(
() => this.users().length > 0 ? Enumerable.from( this.users() ).average( user => user.age() ) : 0 );
minimumAge: KnockoutComputed<number> = ko.computed(
() => this.users().length > 0 ? Enumerable.from( this.users() ).min( user => user.age() ) : 0 );
maximumAge: KnockoutComputed<number> = ko.computed(
() => this.users().length > 0 ? Enumerable.from( this.users() ).max( user => user.age() ) : 0 );

load()
{
this.users( DataClient.load() );
}

store()
{
DataClient.store( this.users() );
}

addUser()
{
var newUser = new User( "", 0 );
this.users.push( newUser );
this.selectedUser( newUser );
}

removeUser( user: User )
{
this.users.remove( user );
}
}

4.4. App.ts

 エントリーポイントです。

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

window.onload = () =>
{
var viewModel = new IndexPageViewModel();
ko.applyBindings( viewModel ); // C# でいう this.DataContext = viewModel;
viewModel.load();
};

4.5. HTML とか CSS とか

 テキトーに作ります (割愛)。

 たとえば、テーブル表示なら、

<table>
<thead>
<tr>
<th>No.</th>
<th>name</th>
<th>age</th>
<th> </th>
</tr>
</thead>
<tbody data-bind="foreach: users">
<tr>
<td class="content-only content-number"><span data-bind="text: $index"></span></td>
<td><input data-bind="value: name" style="width: 7em" /></td>
<td><input data-bind="value: age" style="width: 4em" /></td>
<td><input type="button" data-bind="click: function() { $parent.removeUser( $data ); }" value="x" /></td>
</tr>
</tbody>
</table>

少しだけ解説。

4.5.1. foreach

 xaml の DataTemplate 的なことしたいときに使う。

4.5.2. click

 Click イベントハンドラ―を貼るときに使う

4.5.3. text

 xaml の Path で Binding するみたいなやつ。テキスト要素 span や p に使う。Object の場合、toString が用いられる

4.5.4. value

 xaml の Path で Binding するみたいなやつ。入力要素 input に使う。select の単一選択にも使われる。複数選択は selectedOptions

4.5.5. options

 select の options に使う。中身は text と同じ挙動。

5. サンプル

 上のやつを実際に実装したサンプルを作りました。GitHub にあげております。

https://github.com/mntone/MvvmWithTypeScript

また、すぐ触れるサンプルも。

http://mntone.minibird.jp/mwts/

6. まとめ

 今日の集大成。疲れました。半日これにどっぷり。いい経験になったかな。ちなみになぜやったのかというと、ドットピッチ計算機の改良をするためですね。一応貼っておきます。

おしまい!