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

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

MVCパターンでアプリケーションを構築する(Backbone.js)

2013年5月9日

書籍転載の1本目(書籍内の番号は「86」)。クライアントサイドMVCフレームワークの定番ライブラリの1つ「Backbone.js」の基本的な使い方を解説。

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

書籍転載について

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

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

ご注意

本記事は、書籍の内容を改変することなく、そのまま転載したものです。このため用字用語の統一ルールなどはBuild Insiderのそれとは一致しません。あらかじめご了承ください。

Backbone.jsはクライアントサイドMVCフレームワークの定番ライブラリの1つです。プレゼンテーションをView(ビュー)に、ビジネスロジック(ドメイン)をModel(モデル)に定義するスタイルで処理を記述します。このことにより、コードの保守性、再利用性、テスト可能性などを向上させることができます。

  • 名称: Backbone.js
  • 分類: フレームワーク
  • URL: http://backbonejs.org/
  • 関連ファイル: backbone-0.9.9.js、underscore-1.4.3.js

Backbone.jsとMVCパターン

 Backbone.jsはModel(モデル)、View(ビュー)、Controller(コントローラ)という3つの責務に処理を分割して記述する、いわゆるModel-View-Controller(MVC)パターンに沿ってクライアントサイドの処理を記述します。Modelは検索処理等の業務ロジックを行い、処理結果データを始めとした状態を保持する役割を、Viewはユーザインタフェース(UI)としてユーザとのやり取りを行う役割を、ControllerはModelの状態をViewに伝え、必要があれば数値のカンマ編集などの変換処理(アプリケーションロジック)を行ったり、Viewへの入力を元にModelの処理を実行する役割を持っています。

 アプリケーションを構築する際、小規模なものであればjQuery 等でシンプルに処理を記述することができます。しかし、規模が大きくなってくるとjQueryはコールバック関数を多用することもあり、どうしてもUI操作のためのロジックとAjax通信処理などのアプリケーション、業務ロジックが絡み合い、非常に複雑なコードになってしまいがちです。そのため、どこにどのような処理が書いてあるのか探しにくく修正が容易でなかったり、コードの一部をほかの部分に使いたくても上手くいかなかったりします。

 そこでBackbone.jsによってMVCパターンを考慮してアプリケーションを作成することで、UI操作ロジックとアプリケーションるロジック、さらに業務ロジックを分離、集約することができ、コードの可読性が向上します。また、「イベント」(後述)という仕組みにより業務ロジックがUIに依存しなくなるため、再利用もしやすくなり、QUnit(後日公開予定)などを使ってテストすることも可能になります。

 それでは、Backbone.jsを用いてMVCそれぞれの実装をどのように行うか、Backbone.jsのMVCイメージ図(図086-01)およびGist検索アプリケーションのサンプルと共に、順に説明していきましょう。

図086-01 Backbone.jsのMVCイメージ
図086-01 Backbone.jsのMVCイメージ

 ですがその前に、サンプルアプリケーションについて簡単に動作を説明しましょう。

 GistはGitHubが運営するコード共有サービスで、多くのコードが公開されています。サンプルでは、ユーザに対象ユーザ名を指定して検索ボタンをクリックすると、指定した公開Gistを最大30件検索して、その説明をリンクとともにリストとして表示します(図086-02)。

図086-02 Gist検索アプリケーション

Model:Modelクラス、Collectionクラス

 まず、Modelクラスの定義には、Backbone.Model.extendメソッドを用います。メソッドの引数として、JSON 形式でオプションを指定することで、Modelの動作のカスタマイズが可能です。Modelクラスに既定で用意された主なオプションは表086-01のとおりです。

オプション説明
initialize 初期化を行うメソッド。いわゆるコンストラクタ
defaults set/getメソッドで操作できるModelの属性の既定値を設定するファンクション
validate Modelの検証処理を行うメソッド。setメソッドの内部で呼び出される
表086-01 Modelクラスの主なオプション

 Collection クラスの定義にはBackbone.Collection.extendメソッドを使います。Modelと同様に表086-02のようなオプションが指定できます。

オプション説明
initialize 初期化を行うメソッド。いわゆるコンストラクタ
model Collectionの要素の型
表086-02 Collectionクラスの主なオプション

 では、実際にサンプルのコード(リスト086-01)について解説していきましょう。Modelには前述のとおりアプリケーションの状態であるUIに入力されたユーザと検索結果のリストのデータ、そして処理として検索処理を定義します。

JavaScript
// 1 Gistのリスト項目用Modelクラス
var Gist = Backbone.Model.extend({
  defaults: function () {
    return { gistId: "", description: "", htmlUrl: "" };
  }
});

// 2 Gistのリスト用Collectionクラス
var GistList = Backbone.Collection.extend({ model: Gist });

// 3 アプリケーション用Modelクラス
var App = Backbone.Model.extend({
  // 初期化処理
  initialize: function () {
    this.gistList = new GistList();
  },

  // 検索処理
  search: function () {
    // jQuery.ajaxを使い、指定したユーザのpublic gistのデータを取得
    var gistapiurl = "https://api.github.com/users/" +
      this.get("user") + "/gists?callback=?";
    $.ajax({
      url: gistapiurl,
      type: "GET",
      context: this, // successコールバック関数内のthisを設定
      dataType: "jsonp",
      success: function (response) {
        var data = response.data;
        if (data.message) {
          // 検索処理エラーあり
          // エラーイベントを発生させる
          this.trigger("error", this, data.message);
          return;
        }

        // 検索結果をGistデータリストに反映する
        // (_.each()はUnderscore.jsの機能で、項目を列挙して処理を行うメソッド)
        _.each(data, function (item) {
          this.gistList.add(new Gist({
            gistId: item.id,
            description: item.description,
            htmlUrl: item.html_url
          }));
        }, this);

        // 検索後イベントを発生させる
        this.trigger("searched");
      }
    });
  },

  // 検証処理
  validate: function (attrs) {
    if (!attrs.user) {
      return "ユーザを入力してください";
    }
  }
});
リスト086-01 Model:Modelクラス(models.js)

 1Gistクラスが、Gistの検索結果のデータを格納するModelクラスです。defaultsオプションで3つの属性の初期値を指定しています。

 2GistListクラスは検索結果データであるGistクラスオブジェクトを要素とするCollectionクラスです。

 3AppクラスはGistクラスとGistListクラスを用いてアプリケーションを制御するModelクラスです。Appクラスの主なメンバーには、状態として検索結果を保持するGistListコレクションフィールドを、処理としてserchメソッドを定義します。searchメソッドではユーザを引数で受け取り、jQuery.ajaxメソッドを使った検索を行い、検索結果データをGistListコレクションに設定します。処理が成功したかどうかについては、triggerメソッドでerrorかsearchedイベントを発生させ、後述のViewクラスでハンドリングします。この他、validateメソッドでユーザが入力されたかどうかを検証しています。validateメソッドの戻り値は、errorイベントとともにViewクラスに通知されます。

Controller:Viewクラス

 ControllerはViewクラスで定義します(リスト086-02)。Viewクラスの定義には、Backbone.View.extend メソッドを用います。Model クラスなどと同様に表086-03のようなオプションが指定できます。

JavaScript
// 1 Gistのリスト項目用Viewクラス
var GistView = Backbone.View.extend({
  // 生成するDOMノードの種類を指定
  tagName: "li",

  // 描画処理
  render: function () {
    // Gistの情報を以下の形式で表示する
    // <a href="https://~ " target="_blank">123456 : 説明</a>
    this.$el.append(
      $("<a>").attr("href", this.model.get("htmlUrl"))
        .attr("target", "_blank")
        .text(this.model.get("gistId") + " : " +
          this.model.get("description"))
      );
    return this;
}
});

// 2 アプリケーション用Viewクラス
var AppView = Backbone.View.extend({
  // 起点となる要素を指定
  el: "#gistapp",

  // DOMイベントとメソッドの関連付け
  events: {
    // 検索ボタンのクリックイベントに検索処理を関連付ける
    "click #search": "search",
  },

  // 初期化処理
  initialize: function () {
    // 検索後、描画処理を行う
    this.model.bind("searched", this.render, this);
    // エラー発生時、エラーを表示する
    this.model.bind("error", this.showError, this);
  },

  // 描画処理
  render: function () {
    // Gistデータを元に検索結果を洗い替える
    var $gistlist = $("#gist-list");
    $gistlist.empty();
    this.model.gistList.each(function (gist) {
      // Gistデータ用Viewを使いデータを表示する
      var gistView = new GistView({ model: gist });
      $gistlist.append(gistView.render().el);
    });
  },

  // 検索処理
  search: function () {
    // userの指定値の取得、設定
    var user = $("#user").val();
    this.model.set("user", user);
    // 検索処理を行う
    this.model.search();
  },

  // エラー表示処理
  showError: function (model, error) {
    alert(error);
  }
});
リスト086-02 Controller:Viewクラス(views.js)
オプション説明
initialize 初期化を行うファンクション。いわゆるコンストラクタ
el そのViewクラスが管理するDOM要素のセレクタを指定する
tagName そのViewクラスが担当するDOM要素の名前を指定する
events DOMイベントをViewクラスのメソッドでハンドリングするための設定を行う
render HTMLにどのように描画するか定義するメソッド
model 描画する元データであるModelオブジェクト
表086-03 Viewクラスの主なオプション

 Viewクラスにはユーザーの入力をもとにUIの状態である検索結果データとその表示方法、また処理としてModelの検索結果の呼び出しと処理結果のイベントハンドリングを定義します。

 1 GistView クラスは、検索結果リストの項目用のViewクラスです。jQueryオブジェクトである$elフィールドを編集することで、描画処理を行います。

 2 AppView クラスは、アプリケーションを管理するViewクラスです。状態として検索結果データ、検索条件のユーザを保持し、処理としてDOMイベントのハンドリング、Modelの処理呼び出し、Modelのイベントハンドリング、検索結果の描画を行います。

 DOM イベントをハンドリングするには、events オプションにてView クラスのメソッドを次の形式で指定します。

  イベント名DOM要素のセレクタ:ハンドリングするメ ソッド名

 Modelクラスのイベントをハンドリングするには、Modelクラスのbindメソッドを呼び出します。サンプルではinitializeオプションで行っています。bindメソッドの引数にはイベント名とハンドリングする関数を指定します。

 検索結果の表示はrenderメソッドで行います。GistList コレクションからGist オブジェクトを取り出し、GistViewクラスのオブジェクトに渡すことで、検索結果リストの描画を行います。

 エラーの表示はshowError メソッドで行います。Model のerror イベントをハンドリングする関数の引数には、Modelのvalidate メソッドの戻り値が渡されます。

View : HTML、CSS

 ViewはHTML、CSSを使って定義します(リスト086-03)。Backbone.js として固有の記法などはありませんが、Controllerで扱いやすいよう、適宜DOM要素にidをつけたり、基本的なレイアウトを定めたりする必要があります。

HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Backbone.jsを用いたGist検索サンプル</title>
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.9.0.js"></script>
  <!-- Backbone.jsと依存ライブラリ(Underscoe.js)をインポート -->
  <script type="text/javascript" src="js/underscore-1.4.3.js"></script>
  <script type="text/javascript" src="js/backbone-0.9.9.js"></script>
  <!-- Modelクラスをインポート -->
  <script type="text/javascript" src="js/models.js"></script>
  <!-- Viewクラスをインポート -->
  <script type="text/javascript" src="js/views.js"></script>
  <script type="text/javascript">
    $(function () {
      // 1 アプリケーションModelオブジェクトを作成
      var app = new App();
      // 2 アプリケーションViewオブジェクトを作成
      var appView = new AppView({ model: app });
    });
  </script>
</head>
<body>
  <!-- Viewの定義 -->
  <div id="gistapp">
    <header>
      <h1>Gist 検索</h1>
    </header>
    <section id="condition">
      ユーザ:
      <input type="text" id="user" />
      <button id="search">検索</button>
    </section>
    <section id="result">
      <ol id="gist-list"></ol>
    </section>
  </div>
</body>
リスト086-03 View:HTML(backbone.js.html)

 1 Gist 検索というアプリケーション全体を表すAppクラスのオブジェクトを作成します。

 2 Gist 検索アプリケーション全体を制御するAppViewクラスのオブジェクトを作成します。AppView クラスを作成する際は、オプションとしてAppクラスのオブジェクトを渡します。

処理の流れ

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

図086-03 サンプルアプリケーションの処理の流れ

1検索ボタンクリック

 ユーザ名を入力し、検索ボタンがクリックされます。

2AppViewクラスのsearchメソッド呼び出し

 リスト086-02のAppViewクラスのeventsオプションで指定したように、検索ボタンのクリックイベントがAppView クラスのsearch メソッドでハンドリングされます。

3Appクラスのsearchメソッド呼び出し

 AppView クラスのsearch メソッドでは、入力したユーザの取得とApp クラスのsearch メソッドを呼び出しが行われます(リスト086-02)。App クラスのsearch メソッドではjQuery.ajaxメソッドを使ったGist APIの呼び出しを行い、検索結果をもとにGist クラスオブジェクトの作成と、GistListクラスオブジェクトへの追加が行われます(リスト086-01)。

4Appクラスのsearchedイベント発生

 3の処理の最後でtrigger メソッドによりsearchedイベントが発生します。

5AppViewクラスのrenderメソッド呼び出し

 リスト086-02 のAppView クラスのinitializeオプションで指定したように、searched イベントがAppView クラスのrender メソッドでハンドリングされます。render メソッドの内部では、App クラスよりGistList クラスオブジェクトを取り出し、その項目であるGistクラスオブジェクトの値をもとにGistView クラスのオブジェクトを生成します。したがって、GistViewクラスオブジェクトはそれぞれ1 つのGistクラスオブジェクトに対応しています。

6Gistクラスのgetメソッド呼び出し

 5の中でGistView クラスのrenderメソッドが呼び出されると、Gistクラスオブジェクトのget メソッドを使い、Gist のidなどを取り出し、描画処理を行います。

7検索結果表示

 GistView クラスのrender メソッドの結果生成されたDOM要素を検索結果リストに追加することで、検索結果を表示します。したがって、検索結果の1件1 件はそれぞれ1 つのGistViewクラスオブジェクトと対応しています。

※以下では、本稿の前後を合わせて5回分(第1回~第5回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

1. 【現在、表示中】≫ MVCパターンでアプリケーションを構築する(Backbone.js)

書籍転載の1本目(書籍内の番号は「86」)。クライアントサイドMVCフレームワークの定番ライブラリの1つ「Backbone.js」の基本的な使い方を解説。

2. Backbone.jsのModelを操作し、イベントを購読する

書籍転載の2本目(書籍内の番号は「87」)。Backbone.jsのModelの内部状態を変更するメソッドが呼ばれた際に発生するchangeイベントを購読することで、Modelの状態変更を監視する方法を説明。

3. Backbone.jsのCollectionを操作し、イベントを購読する

書籍転載の3本目(書籍内の番号は「88」)。Backbone.jsのCollectionの内部状態を変更するメソッドが呼ばれた際に発生するadd/change/remove/resetなどのイベントを購読することで、Collectionの状態変更を監視する方法を説明。

4. Ember.jsでビューとコントローラを紐付け、DOM更新のコードを省略する

書籍転載の4本目(書籍内の番号は「71」)。MVCフレームワークの「Ember.js」の基礎と基本的な使い方を紹介。Ember.jsではテンプレート・エンジンにより、コントローラからのデータを受け取ってHTML出力内容を動的に整形できる。

5. [AngularJS]HTMLそのものをテンプレートとして動的な表示を実現する

書籍転載の5本目(書籍内の番号は「70」)。HTMLとJavaScriptをシンプルに分離できる、LiteなJavaScriptフレームワークである「AngularJS」の基礎と基本的な使い方を紹介。

サイトからのお知らせ

Twitterでつぶやこう!