ちょっとJavaScriptのフレームワークを使おうかなと思ったのでどれを使おうか選んでたのですが、最近のreact.jsや、AngularJSや、Cycle.jsとかもいいですが以下の理由でKnockout.jsにしてみようと思いました。
- 枯れてる
- メンテナンスされ続けてる
- 学習コストが低い
- TypeScriptと相性がよさそうに見えた
特に半年前のフレームワークって何処行ったの??っていう状況になってるような気がするJavaScript界においてこれだけ長期間安定して提供され続けてるという点が個人的に評価ポイントとして高かったです。
Visual Studioでの使い方
ということでVSから使ってみようと思います。ASP.NETのプロジェクトを作ってMVCにだけチェック入れてEmptyのプロジェクトを作ります。
ライブラリの導入
NuGetからknockoutjsをインストールします。続けてknockout.typescriptで検索して型定義を入れます。
Viewの作成
HomeControllerを作ってIndexのViewをさくっと作ります。HomeControllerのIndexメソッドで右クリックしてViewの追加からやるとスムーズです。
Views/Shared/_Layout.cshtmlのJavaScriptに以下のようにknockoutを読み込むようにします。
<!DOCTYPE html><html><head><metacharset="utf-8" /><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>@ViewBag.Title - My ASP.NET Application</title><linkhref="~/Content/Site.css"rel="stylesheet"type="text/css" /><linkhref="~/Content/bootstrap.min.css"rel="stylesheet"type="text/css" /><scriptsrc="~/Scripts/modernizr-2.6.2.js"></script><scriptsrc="~/Scripts/knockout-3.3.0.js"></script>></head><body><divclass="navbar navbar-inverse navbar-fixed-top"><divclass="container"><divclass="navbar-header"><buttontype="button"class="navbar-toggle"data-toggle="collapse"data-target=".navbar-collapse"><spanclass="icon-bar"></span><spanclass="icon-bar"></span><spanclass="icon-bar"></span></button> @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" }) </div><divclass="navbar-collapse collapse"><ulclass="nav navbar-nav"></ul></div></div></div><divclass="container body-content"> @RenderBody() <hr /><footer><p>© @DateTime.Now.Year - My ASP.NET Application</p></footer></div><scriptsrc="~/Scripts/jquery-1.10.2.min.js"></script><scriptsrc="~/Scripts/bootstrap.min.js"></script></body></html>
Scriptの作成
Scriptsフォルダにindex.tsという名前でTypeScriptのファイルを作ります。ここにいろいろ書いていきます。
とりあえず、以下のページのサンプルをさくっと書いてみようと思います。
Hello world
TypeScriptはこんな感じで。
/// <reference path="typings/knockout/knockout.d.ts" /> module ViewModels { export class HelloWorldViewModel { public firstName: KnockoutObservable<string>; public lastName: KnockoutObservable<string>; public fullName: KnockoutComputed<string>; constructor(firstName: string, lastName: string) { this.firstName = ko.observable(firstName); this.lastName = ko.observable(lastName); this.fullName = ko.computed(() => this.firstName() + this.lastName()) } } } window.onload = () => ko.applyBindings(new ViewModels.HelloWorldViewModel("Planet", "Earth"));
Index.cshtmlはこんな感じで。
@{ ViewBag.Title = "Index"; } <p>ファーストネーム:<inputdata-bind="value: firstName" /></p><p>ラストネーム:<inputdata-bind="value: lastName" /></p><h2>Hello, <spandata-bind="text: fullName"></span>!</h2><scriptsrc="~/Scripts/index.js"></script>
クリックカウンター
TypeScriptはこんな感じ。
/// <reference path="typings/knockout/knockout.d.ts" /> module ViewModels {exportclass ClickCounterViewModel {public numberOfClicks: KnockoutObservable<number> = ko.observable(0); public registerClick(): void {this.numberOfClicks(this.numberOfClicks() + 1); }public resetClicks(): void {this.numberOfClicks(0); }public hasClickedTooManyTimes: KnockoutComputed<boolean> = ko.computed(() => {returnthis.numberOfClicks() >= 5; }, this); }}window.onload = () => ko.applyBindings(new ViewModels.ClickCounterViewModel());
HTMLはこんな感じ。
@{ ViewBag.Title = "Index"; } <div>クリック回数 <spandata-bind="text: numberOfClicks"></span>回</div><buttondata-bind="click: registerClick, disable: hasClickedTooManyTimes">クリック</button><divdata-bind="visible: hasClickedTooManyTimes">クリックしすぎ。 <buttondata-bind="click: resetClicks">リセット</button></div><scriptsrc="~/Scripts/index.js"></script>
シンプルなリスト
TypeScriptはこんな感じ。
/// <reference path="typings/knockout/knockout.d.ts" /> module ViewModels {exportclass SimpleListViewModel {public items: KnockoutObservableArray<string>; public itemToAdd: KnockoutObservable<string> = ko.observable(""); constructor(items: string[]) {this.items = ko.observableArray(items); }public addItem(): void {this.items.push(this.itemToAdd()); this.itemToAdd(""); }}}window.onload = () => ko.applyBindings(new ViewModels.SimpleListViewModel(["Alpha", "Beta", "Gamma"]));
HTMLはこんな感じ。
@{ ViewBag.Title = "Index"; } <formdata-bind="submit: addItem">新しいアイテム <inputdata-bind="value: itemToAdd" /><buttondata-bind="click: addItem">追加</button><p>アイテム一覧</p><selectmultiple="multiple"data-bind="options: items"></select></form><scriptsrc="~/Scripts/index.js"></script>
リストを改良する
ちょっとめんどくさくなってきたので手抜きでこんな感じに。
/// <reference path="typings/knockout/knockout.d.ts" /> module ViewModels {exportclass SimpleListViewModel {public items: KnockoutObservableArray<string>; public selectedItems: KnockoutObservableArray<string>; public itemToAdd: KnockoutObservable<string> = ko.observable(""); constructor(items: string[]) {this.items = ko.observableArray(items); this.selectedItems = ko.observableArray(items.slice(0, 1)); }public addItem(): void {this.items.push(this.itemToAdd()); this.itemToAdd(""); }public removeSelected(): void {this.items.removeAll(this.selectedItems()); this.selectedItems([]); }public sort(): void {this.items.sort(); }}}window.onload = () => ko.applyBindings(new ViewModels.SimpleListViewModel(["Alpha", "Beta", "Gamma"]));
HTMLのほうはこんな感じに。
@{ ViewBag.Title = "Index"; } <formdata-bind="submit: addItem">新しいアイテム <inputdata-bind="value: itemToAdd" /><buttondata-bind="click: addItem">追加</button><p>アイテム一覧</p><selectmultiple="multiple"data-bind="options: items, selectedOptions: selectedItems"></select><buttondata-bind="click: removeSelected, enable: selectedItems().length > 0">削除</button><buttondata-bind="click: sort, enable: items().length > 0">ソート</button></form><scriptsrc="~/Scripts/index.js"></script>
コレクションを操る
段々めんどくさくなってきたので$rootとかは省略
/// <reference path="typings/knockout/knockout.d.ts" /> module ViewModels {exportclass Person {public children: KnockoutObservableArray<string>; constructor(public name: string, children: string[]) {this.children = ko.observableArray(children); }public addChild(): void {this.children.push("新しいお子様"); }}exportclass ViewModel {public people: Person[]; constructor() {this.people = [new Person("Annabella", ["Arnie", "Anders", "Apple"]), new Person("Bertie", ["Arnie", "Anders", "Apple"]), new Person("Charles", ["Arnie", "Anders", "Apple"]) ]; }}}window.onload = () => ko.applyBindings(new ViewModels.ViewModel());
HTML
@{ ViewBag.Title = "Index"; } <uldata-bind="foreach: people"><li><div><spandata-bind="text: name"></span>さんの<spandata-bind="text: children().length"></span>人のお子様 <ahref="#"data-bind="click: addChild">また産まれた!</a><uldata-bind="foreach: children"><li><spandata-bind="text: $data"></span></li></ul></div></li></ul><scriptsrc="~/Scripts/index.js"></script>
力尽きた
とりあえず、眠たくなってきたので今日はここまで。