書籍転載:JavaScriptライブラリ実践活用[厳選111]

書籍転載:JavaScriptライブラリ実践活用[厳選111]

[Knockout]MVVMパターンでアプリケーションを構築する

2013年5月30日

書籍転載の7本目(書籍内の番号は「89」)。MVVM(Model-View-ViewModel)パターンをサポートするJavaScriptライブラリである「Knockout」の基礎と、基本的な使い方を解説。

WINGSプロジェクト 高野 将
  • このエントリーをはてなブックマークに追加

書籍転載について

 本コーナーは、技術評論社発行の書籍『JavaScriptライブラリ実践活用[厳選111]』の中から、特にBuild Insiderの読者に有用だと考えられる項目を編集部が選び、同社の許可を得て転載したものです。

 『JavaScriptライブラリ実践活用[厳選111]』の詳細や購入は技術評論社のサイト目次ページをご覧ください。

ご注意

本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどは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コードで構成されます。

図089-01 KnockoutのMVVMイメージ
図089-01 KnockoutのMVVMイメージ

 Knockoutを用いることで、次のような利点があります。

  • DOM操作のためのJavaScriptコードがほとんど不要になる
  • UIへの入出力のためにHTML要素へのid設定が不要になる
  • DOM構造を意識せずにModelを作成できる

 Knockoutを使ったMVVMパターンの例をサンプルコードを使って説明していきましょう。

 サンプルの動作を簡単に説明しておきましょう。Backbone.jsと同じように、ユーザを指定してGistを検索するアプリケーションです(図089-02)。

図089-02 Gistを検索するアプリケーション
図089-02 Gistを検索するアプリケーション

Modelの定義

 アプリケーションを制御するModelのサンプルをリスト089-01に示します。

JavaScript
// 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;
})();
リスト089-01 Model (appmodel.js)

ViewModelの定義

 ViewModelもModelと同様にJavaScriptのクラスとして定義します。サンプルではGist検索結果リスト1件分の表示を担当するGistViewModelクラス(リスト089-02)、アプリケーション全体の表示を担当するAppViewModelクラス(リスト089-03)を定義します。

JavaScript
// 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;
})();
リスト089-02 GistViewModel定義(gistviewmodel.js)
JavaScript
// アプリケーション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;
})();
リスト089-03 AppViewModel定義(appviewmodel.js)

Viewの定義

 ViewはHTMLを使って定義し、ViewModelをもとに描画するためのテンプレートを記載していくイメージになります(リスト089-04)。

HTML
<!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>
リスト089-04 Viewの定義
記法説明
value: (ViewModelのフィールド名) value属性へのバインド
text: (ViewModelのフィールド名) text(タグの内側のテキスト)へのバインド
attr: {(バインド対象属性): (ViewModelのフィールド名), ...} value、text以外の属性へのバインド
click: (ViewModelのメソッド名) clickイベントへのViewModelファンクションのバインド
foreach: (ViewModelのコレクションフィールド名) ViewModelのコレクションからのバインド
表089-01 data-bind属性で使用できるの主な記法

 今回紹介したもの以外にもさまざまな記法が使えます。詳しくは公式サイトのドキュメントを参照してください。

処理の流れ

 コードを見ただけではどのように処理が流れていくかわかりにくいと思いますので順に説明します。

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」の基礎として、さまざまなコレクション操作する方法を説明。

サイトからのお知らせ

Twitterでつぶやこう!