書籍転載:JavaScriptライブラリ実践活用[厳選111]
[Knockout]MVVMパターンでアプリケーションを構築する
書籍転載の7本目(書籍内の番号は「89」)。MVVM(Model-View-ViewModel)パターンをサポートするJavaScriptライブラリである「Knockout」の基礎と、基本的な使い方を解説。
書籍転載について
ご注意
本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。
KnockoutはModel-View-ViewModel(MVVM)パターンをサポートするライブラリです。Knockoutではデータバインドを用いて、宣言的にView(HTML)とViewModelを関連付けます。そうすると、Viewを変更すればViewModelが、ViewModelを変更すればViewがというように、一方の変更がもう一方に自動的に反映されるようになります。また、Viewのボタンクリックなどのアクションについても、データバインドを使ってViewModelのメソッドと関連付けることができます。
- 名称: Knockout
- 分類: フレームワーク
- URL: http://knockoutjs.com/
- 関連ファイル: knockout-2.2.0.js
MVVMパターンとは
Model-View-ViewModel(MVVM)パターン とはプレゼンテーション(Presentation)とドメイン(Domain)の分割を目的としたMVC系のパターンの1つで、アプリケーションのコードをModel、View、ViewModelという3つの責務に分割して記述します。ここでのプレゼンテーションとはユーザインタフェース(UI)を実装するプラットフォームに依存した部分、ドメインはプラットフォームに依存しない部分を指しています。Webアプリで言えば、プレゼンテーションはHTMLの都合が関係ある部分、ドメインはHTMLの都合が関係ない部分になります。
MVVMパターンではプレゼンテーションをViewとViewModelが担当し、ドメインをModelが担当します。MVVMパターンの詳細は次のスライドなどを参考にしてください。
KockoutでのMVVMパターン
Knockoutを使ったMVVMパターンは図089-01のようになります。ModelはWebサービス呼び出しなどを行うドメインロジックを実装するJavaScriptのコード、ViewはUIのテンプレートを定義するHTML、ViewModelはViewへのデータバインドのためにViewの状態を保持し、プレゼンテーションロジックを実装するJavaScriptコードで構成されます。
Knockoutを用いることで、次のような利点があります。
- DOM操作のためのJavaScriptコードがほとんど不要になる
- UIへの入出力のためにHTML要素へのid設定が不要になる
- DOM構造を意識せずにModelを作成できる
Knockoutを使ったMVVMパターンの例をサンプルコードを使って説明していきましょう。
サンプルの動作を簡単に説明しておきましょう。Backbone.jsと同じように、ユーザを指定してGistを検索するアプリケーションです(図089-02)。
Modelの定義
アプリケーションを制御するModelのサンプルをリスト089-01に示します。
// 1 アプリケーションModel定義
var AppModel = (function () {
// クラス定義、コンストラクタ
function AppModel() {
// 2 フィールド定義
// Gistデータコレクション
this.gists = ko.observableArray();
}
// 3 メソッド定義
// 検索処理
AppModel.prototype.search = function (user) {
// jQuery.ajaxを使い、指定したユーザのpublic gistのデータを取得
var gistapiurl = "https://api.github.com/users/" +
user + "/gists?callback=?";
$.ajax({
url: gistapiurl,
type: "GET",
context: this, // successコールバック関数内のthisを設定
dataType: "jsonp",
success: function (response) {
var data = response.data;
if (data.message) return; // エラーがあったら中断
this.gists(data);
}
});
};
return AppModel;
})();
|
ViewModelの定義
ViewModelもModelと同様にJavaScriptのクラスとして定義します。サンプルではGist検索結果リスト1件分の表示を担当するGistViewModelクラス(リスト089-02)、アプリケーション全体の表示を担当するAppViewModelクラス(リスト089-03)を定義します。
// Gistのデータ用ViewModel
var GistViewModel = (function () {
// クラス定義、コンストラクタ
function GistViewModel(id, description, html_url) {
// 1 フィールド定義
// Gist ID
this.id = ko.observable(id);
// 説明
this.description = ko.observable(description);
// GistページURL
this.html_url = ko.observable(html_url);
// 2 組み合わせフィールド定義
// Gistの情報を以下の形式で表示するためのテキスト部分
// <a href="https://......" target="_blank">123456 : 説明</a>
this.text = ko.computed(function () {
return this.id() + " : " + this.description();
}, this);
}
return GistViewModel;
})();
|
// アプリケーションViewModel
var AppViewModel = (function () {
// クラス定義、コンストラクタ
function AppViewModel(model) {
// 1 フィールド定義
// ユーザ
this.user = ko.observable();
// Gistデータコレクション
this.gists = ko.observableArray();
// AppModel
this.model = model;
// 2 Modelの状態変更購読
this.model.gists.subscribe(function (gists) {
// Gistデータ用ViewModelコレクションを洗い替え
var newGists = $.map(gists, function (item, index) {
return new GistViewModel(item.id, item.description,
item.html_url);
});
this.gists(newGists);
}, this);
}
// 3 メソッド定義
// 検索処理
AppViewModel.prototype.search = function () {
// 既存の検索結果をクリア
this.gists.removeAll();
// 入力されたユーザを登録
// Modelの検索処理実行
this.model.search(this.user());
};
return AppViewModel;
})();
|
Viewの定義
ViewはHTMLを使って定義し、ViewModelをもとに描画するためのテンプレートを記載していくイメージになります(リスト089-04)。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Gist検索</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.0.js"></script>
<!-- Knockoutをインポート -->
<script type="text/javascript" src="js/knockout-2.2.0.js"></script>
<!-- Modelをインポート -->
<script type="text/javascript" src="js/appmodel.js"></script>
<!-- ViewModelをインポート -->
<script type="text/javascript" src="js/gistviewmodel.js"></script>
<script type="text/javascript" src="js/appviewmodel.js"></script>
<script type="text/javascript">
// 1 データバインドの有効化
$(function () {
var model = new AppModel();
var viewModel = new AppViewModel(model);
ko.applyBindings(viewModel);
});
</script>
</head>
<body>
<!-- 2 Viewの定義 -->
<div>
<header>
<h1>Gist検索</h1>
</header>
<!-- 検索条件 -->
<section>
ユーザ
<input type="text" data-bind="value: user" />
<button data-bind="click: search">検索</button>
</section>
<!-- 検索結果 -->
<section>
<ol data-bind="foreach: gists">
<li>
<a href="#" data-bind="attr: { href: html_url }, text: text" target="_blank"></a>
</li>
</ol>
</section>
</div>
</body>
</html>
|
記法 | 説明 |
---|---|
value: (ViewModelのフィールド名) | value属性へのバインド |
text: (ViewModelのフィールド名) | text(タグの内側のテキスト)へのバインド |
attr: {(バインド対象属性): (ViewModelのフィールド名), ...} | value、text以外の属性へのバインド |
click: (ViewModelのメソッド名) | clickイベントへのViewModelファンクションのバインド |
foreach: (ViewModelのコレクションフィールド名) | ViewModelのコレクションからのバインド |
今回紹介したもの以外にもさまざまな記法が使えます。詳しくは公式サイトのドキュメントを参照してください。
処理の流れ
コードを見ただけではどのように処理が流れていくかわかりにくいと思いますので順に説明します。
1 検索ボタンクリック
ユーザ名を入力し、検索ボタンがクリックされます。
2 AppViewModelクラスのsearchメソッド呼び出し
リストX3にて検索ボタンのclickイベントにAppViewModelクラスのsearchメソッドにデータバインドしているため、searchメソッドが実行されます。
3 AppModelクラスのsearchメソッド呼び出し
AppViewModelクラスのsearchメソッドでは、AppModelクラスのsearchメソッドを呼び出しが行われます(リスト089-02)。AppModelクラスのsearchメソッドではjQuery.ajaxメソッドを使ったGist APIの呼び出しを行い、検索結果がgistsフィールドへ設定されます(リスト089-01)。
4 AppModelのgistsフィールド変更の購読
リスト089-02にてAppModelのgistsフィールドをsubscribeメソッドにより購読しているため、AppModelのgistsフィールドを元にしてAppViewModelのgistsフィールドを設定します。その値はGistViewModelオブジェクトのコレクションにします。
5 Gist検索結果表示
リスト089-03にてAppViewModelのgistsフィールドを検索結果欄にデータバインドしているため、4によりAppView Modelのgistsフィールドが変更されると自動的に検索結果が再表示されます。
※以下では、本稿の前後を合わせて5回分(第5回~第9回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
5. [AngularJS]HTMLそのものをテンプレートとして動的な表示を実現する
書籍転載の5本目(書籍内の番号は「70」)。HTMLとJavaScriptをシンプルに分離できる、LiteなJavaScriptフレームワークである「AngularJS」の基礎と基本的な使い方を紹介。
6. [Sammy.js]URLによって処理を分割する
書籍転載の6本目(書籍内の番号は「77」)。URLの「#」以降の指定によって処理を分割することに着目したフレームワークである「Sammy.js」の基礎と基本的な使い方を紹介。
7. 【現在、表示中】≫ [Knockout]MVVMパターンでアプリケーションを構築する
書籍転載の7本目(書籍内の番号は「89」)。MVVM(Model-View-ViewModel)パターンをサポートするJavaScriptライブラリである「Knockout」の基礎と、基本的な使い方を解説。
8. [QUnit]テストコードを実行し、ブラウザで結果を確認する
書籍転載の8本目(書籍内の番号は「111」)。JavaScript用テストランナーの定番ライブラリである「QUnit」の基礎と基本的な使い方を説明。
9. [Underscore.js]さなざまなコレクション操作を行う
書籍転載の9本目(書籍内の番号は「100」)。ユーティリティ・ライブラリ「Underscore.js」の基礎として、さまざまなコレクション操作する方法を説明。