Backbone 入門

Ajax Web アプリケーションに MVC (モデル・ビュー・コントローラー) 構造を導入する方法

Web アプリケーションに含まれる膨大な行数の JavaScript コードを効率的に管理するのは難しい課題です。しかし、ユーザーにより良いエクスペリエンスを提供するために、Web アプリケーションの各ページのコンテンツをロードする手段として Ajax (Asynchronous JavaScript and XML) が大量に使用されています。また、より一般的になりつつあるシングル・ページ・インターフェースにも、Ajax が使用されています。JavaScript フレームワークとして Backbone を使用すると、MVC (モデル・ビュー・コントローラー) に似たスタイルのアプリケーションやシングル・ページ・インターフェースを作成することができます。この記事では、Ajax アプリケーションやシングル・ページ・インターフェースを作成する上で、いかに Backbone が有効であるかを解説します。

Sebastiano Armeli-Battana, Software Engineer, Freelance

Sebastiono Armeli BattanaSebastiano Armeli-Battana は、JavaScript および Java 開発を専門とするソフトウェア・エンジニアです。Web 技術に情熱を傾けている彼は、Java 技術を採用している SMS Management & Technology でコンサルタントを務める傍ら、フリーランスの Web エンジニアとしても働いています。彼の著書には、『jQuery plug-in JAIL』があります。彼個人のサイトは http://www.sebastianoarmelibattana.com です。



2012年 1月 27日

はじめに

クライアント・サイドのスクリプトや Ajax を使用することで、ますます Web アプリケーションの重点はフロントエンドに置かれるようになっています。JavaScript アプリケーションが複雑になるにつれ、反復作業がなく効率的で保守が容易な JavaScript コードの作成は、適切なツールとパターンを使用しなければ困難になっています。MVC (モデル・ビュー・コントローラー) は、構成が適切で保守が容易なコードを作成するためにサーバー・サイドで使用される一般的なパターンです。MVC ではデータ (Ajax によく使用される JSON (JavaScript Object Notation) オブジェクトなど) をページのプレゼンテーション層、つまり DOM (Document Object Model) から分離することができますが、この MVC をクライアント・サイドの開発にも適用することができます。

Backbone (Backbone.js とも呼ばれます) は、Jeremy Ashkenas 氏によって作成された軽量のライブラリーであり、MVC に似たスタイルのアプリケーションの作成に活用することができます。Backbone の特徴は以下のとおりです。

  • ユーティリティー・ライブラリー underscore.js に大きく依存しています。
  • jQuery/Zepto に少し依存しています。
  • モデルの変更内容に応じてアプリケーションの HTML を自動的に更新するため、コードの保守が容易になります。
  • クライアント・サイドでのテンプレート機能の使用が促進されるため、JavaScript に HTML を埋め込む必要がなくなります。

Backbone フレームワークの主なコンポーネントは、モデル、ビュー、コレクション、そしてルーターです。Backbone では、モデルは RESTful な JSON インターフェースによってサーバーから取得したデータを格納します。モデルはビューと関連付けられ、ビューは特定の UI コンポーネントの HTML を描画し、ビューの構成要素上でトリガーされたイベントを処理します。

頻繁に使用される略語

  • DOM: Document Object Model
  • MVC: Model-View-Controller
  • SPI: Single Page Interface

この記事では、Backbone.js フレームワークのさまざまなコンポーネントについて説明し、Backbone に MVC がどのように適用されるかを詳しく探ります。また、Ajax アプリケーションや SPI (Single Page Interface: シングル・ページ・インターフェース) を作成する際に、Backbone がいかに有効であるかをさまざまな例をとおして示します。

この記事で使用するソース・コードをダウンロードしてください。


SPI アプリケーション: Backbone.RouterBackbone.history

Ajax を大量に使用するアプリケーションは、ページを最新の情報に更新する必要のないアプリケーションのようになりつつあります。これらのアプリケーションは多くの場合、1 つのページのみでやり取りを行おうとします。この SPI の手法により、効率と速度が高まり、アプリケーション全体の応答性が向上します。「ページ」という概念は「状態」という概念で置き換えられ、個別の状態を特定するために「ハッシュ・フラグメント」が使用されます。ハッシュ・フラグメントは URL のハッシュ・タグ (#) に続く部分であり、この種のアプリケーションにとって重要な要素です。リスト 1 は、SPI アプリケーション内部の 2 つの異なる状態を、2 つの異なるハッシュ・フラグメントを使用して表現しています。

リスト 1. SPI アプリケーション、つまり Ajax アプリケーション内の 2 つの異なる状態の表現
http://www.example.com/#/state1
http://www.example.com/#/state2

Backbone には、(バージョン 0.5 よりも前は「コントローラー」と呼ばれていた)「ルーター」と呼ばれるコンポーネントがあり、このルーターがクライアント・サイドの状態をルーティングします。ルーターは Backbone.Router 関数を継承し、状態とアクションとを関連付けるハッシュ・マップ (routes 属性) を含んでいます。ハッシュ・マップでアクションと関連付けられたいずれかの状態にアプリケーションがなると、その状態に対応するアクションが実行されます。リスト 2 は Backbone のルーターの例を示しています。

リスト 2. Backbone.Router の例: routers.js
App.Routers.Main = Backbone.Router.extend({
    
   // Hash maps for routes
   routes : {
      "" : "index",
      "/teams" : "getTeams",
      "/teams/:country" : "getTeamsCountry",
      "/teams/:country/:name : "getTeam"
      "*error" : "fourOfour"
   },
   
   index: function(){
       // Homepage 
   },
   
   getTeams: function() {
       // List all teams 
   },
   getTeamsCountry: function(country) {
       // Get list of teams for specific country
   },
   getTeam: function(country, name) {
       // Get the teams for a specific country and with a specific name
   },	
   fourOfour: function(error) {
       // 404 page
   }
});

作成された各状態には、ブックマークを付けることができます。上記リストの 5 つのアクション (indexgetTeamsgetTeamsCountrygetTeamCountryfourOfour) は、それぞれ下記タイプの URL が指定された場合に呼び出されます。

  • http://www.example.com は、index() を実行します。
  • http://www.example.com/#/teams は、getTeams() を実行します。
  • http://www.example.com/#/teams/country1 は、パラメーターとして country1 を渡して getTeamsCountry() を実行します。
  • http://www.example.com/#/teams/country1/team1 は、パラメーターとして country1team1 を渡して getTeamCountry() を実行します。
  • http://www.example.com/#/something は、上記以外のハッシュ・フラグメントが使用された場合に fourOfour() を実行します。

Backbone を起動するためには、ページのロード時にルーターをインスタンス化し、Backbone.history.start() メソッドを呼び出すことによってハッシュ・フラグメントが変更されるのをモニターします (リスト 3)。

リスト 3. (jQuery を使用した) アプリケーションの初期化
$(function(){
    var router = new App.Routers.Main();
    Backbone.history.start({pushState : true});
})

Backbone.history オブジェクトはルーターがインスタンス化されると生成されます。このオブジェクトは Backbone.History 関数を自動的に参照します。Backbone.history は、ルートを router オブジェクトに定義されたアクションに対応させます。start() メソッドが実行されると、Backbone.history 内に fragment 属性が作成されます。fragment 属性にはハッシュ・フラグメントの値が含まれています。このシーケンスは、状態のシーケンスに従ってブラウザーの履歴を管理する上で役に立ちます。ユーザーが現在の前の状態に戻りたい場合には、ブラウザーの「戻る」ボタンをクリックします。

リスト 3 の例では、HTML5 の pushState 機能が有効にされた構成パラメーターを指定して start() メソッドが呼び出されています。ブラウザーが pushState をサポートしている場合には、Backbone は、新しい状態を呼び出すための popstate イベントをモニターします。ブラウザーが pushState をサポートしていない場合には onhashchange イベントをモニターします。このイベントをブラウザーがサポートしていない場合には、ポーリングによって URL のハッシュ・フラグメントが変更されていないかモニターします。


モデルとコレクション

モデルとコレクションは Backbone.js の重要なコンポーネントです。モデルは (通常はサーバーから受信する) データをキーと値のペアとして保持します。モデルを作成するためには Backbone.Model を継承します (リスト 4)。

リスト 4. Backbone.Model によるモデルの作成
App.Models.Team = Backbone.Model.extend({
    defaults : {
       // default attributes
    }
    // Domain-specific methods go here
});

App.Models.Team 関数は新しいモデル関数ですが、アプリケーションの中で特定のモデルを使用するためには、この関数のインスタンスを作成する必要があります (リスト 5)。

リスト 5. モデル関数のインスタンス化
var team1 = new App.Models.Team();

これによって team1 変数は cid というフィールドを持つようになります。cid は “c” という文字に数字を続けた形式 (c0、c1、c2 など) のクライアント識別子です。モデルはハッシュ・マップに格納される属性によって定義されます。属性は、モデルをインスタンス化する際に設定することも、set() メソッドを使用して設定することもできます。一方、属性の値を取得するためには get() メソッドを使用します。リスト 6 は、インスタンス化の際に属性を設定する方法、set() メソッドによって属性を設定する方法、そして get() メソッドによって属性を取得する方法を示しています。

リスト 6. モデルのインスタンス化の際および get()/set() メソッドによる属性の設定と取得
// "name" attribute is set into the model
var team1 = new App.Models.Team({
    name : "name1"
});
console.log(team1.get("name")); // prints "name1"

// "name" attribute is set with a new value
team1.set({
    name : "name2"
});
console.log(team1.get("name")); //prints "name2"

JavaScript オブジェクトを扱う際に、属性を作成したり、属性の値を設定したりするために、set() メソッドを使用する理由が明らかでないかもしれません。その理由の 1 つは、属性の値を更新するためです (リスト 7)。

リスト 7. 属性の値を更新する誤った方法
team1.attributes.name = "name2";

リスト 7 のコードを使用するのは避けてください。モデルの状態を変更し、そのモデルの変更イベントをトリガーする唯一の手段は、set() を使用することです。set() を使用すると、カプセル化が行われることになります。下記のリスト 8 に、イベント・ハンドラーを変更イベントにバインドする方法を示します。このイベント・ハンドラーにはアラートが含まれており、このアラートはリスト 6set() メソッドが呼び出されると実行されますが、リスト 7 のコードでは実行されません。

リスト 8. App.Models.Team モデルの変更イベント・ハンドラー
App.Models.Team = Backbone.Model.extend({
    initialize : function(){
        this.bind("change", this.changed);
    },
    changed : function(){
        alert("changed");
    }
});

Backbone を使用することでもたらされるメリットの 1 つは、Ajax によってサーバーと容易に通信できることです。モデルの save() メソッドを呼び出すと、(属性のハッシュ・マップによって表現された) 現在の状態を REST によって JSON 形式でやりとりするための API を使用して非同期でサーバーに保存することができます。

リスト 9 は 1 つの例を示しています。

リスト 9. モデル・オブジェクトで呼び出される save メソッド
barca.save();

save() 関数は、実際には処理を Backbone.sync に委譲します。Backbone.sync はデフォルトで jQuery の $.ajax() 関数を使用して RESTful なリクエストを行うコンポーネントです。REST スタイルのアーキテクチャーが使用されているため、CRUD (Create、Read、Update、Delete) の各アクションは異なるタイプの HTTP リクエスト (POSTGETPUTDELETE) と関連付けられます。モデル・オブジェクトが初めて保存される場合には POST リクエストが使用され、識別子 ID が作成されます。その後でモデル・オブジェクトをサーバーに送信する場合には PUT リクエストが使用されます。

サーバーからモデルを取得する場合には Read アクションがリクエストされ、Ajax の GET リクエストが使用されます。このタイプのリクエストは fetch() メソッドを使用します。モデルのデータの保存先あるいは取得元となるサーバーの場所は以下のようにして決定します。

  • モデルがコレクションに属している場合には、コレクション・オブジェクトの url 属性を URL のベースとし、モデルの ID (cid ではありません) を追加して完全な URL にします。
  • モデルがコレクションに属していない場合には、モデルの urlRoot 属性を URL のベースとして使用します。

リスト 10 はモデルを取得する方法を示しています。

Listing 10. Fetch() method for a model object
var teamNew = new App.Models.Team({
    urlRoot : '/specialTeams'
});
teamNew.save(); // returns model's ID equal to '222'
teamNew.fetch(); // Ajax request to '/specialTeams/222'

モデルの検証には validate() メソッドを使用します (リスト 11)。validate() メソッドは set() メソッドが呼び出されると実行され、この validate() メソッドをオーバーライドしてモデルの検証ロジックを含める必要があります。この関数に渡される唯一のパラメーターは、set() メソッドによって更新される属性を含む JavaScript オブジェクトです。この方法により、これらの属性の状態を検証することができます。validate() メソッドから何も返されない場合には、検証結果は成功です。エラー・メッセージが返される場合、検証結果は失敗であり、set() メソッドは実行されません。

リスト 11. モデルを検証するためのメソッド
App.Models.Team = Backbone.Model.extend({
    validate : function(attributes){
        if (!!attributes && attributes.name === "teamX") {
            // Error message returned if the value of the "name" 
            // attribute is equal to "teamX"
            return "Error!";
        }
    }
}

一連のモデルは Backbone.Collection 関数を継承するコレクションとしてグループ化されます。コレクションは、そのコレクションを構成するモデルのタイプを定義するモデル属性によって表現されます。コレクションに対してモデルを追加する場合や、コレクションからモデルを削除する場合には、add() メソッドや remove() メソッドを使用します。リスト 12 は、コレクションの作成方法とコレクションへのモデルの追加方法を示しています。

リスト 12. Backbone のコレクション
App.Collections.Teams = Backbone.Collection.extend({
    model : App.Models.Team
});
var teams = new App.Collections.Teams();

// Add e model to the collection object "teams"
teams.add(team1);
teams.add(new App.Models.Team({
    name : "Team B"
}));
teams.add(new App.Models.Team());
teams.remove(team1);

console.log(teams.length) // prints 2

作成される teams コレクションは、モデル属性に格納される 2 つのモデルからなる配列を含んでいます。ただし通常の Ajax アプリケーションでは、コレクションへの追加は (手動ではなく) サーバーから動的に行われます。それを行うのが fetch() メソッドであり (リスト 13)、fetch() メソッドはデータをモデルの配列に格納します。

リスト 13. fetch() メソッド
teams.fetch();

Backbone のコレクションには url 属性があり、この url 属性により、Ajax の GET リクエストで JSON データを取得する元となるサーバー上の場所が定義されます (リスト 14)。

リスト 14. コレクションの url 属性と fetch() メソッド
teams.url = '/getTeams';
teams.fetch(); //Ajax GET Request to '/getTeams'

fetch() メソッドは非同期呼び出しです。そのため、サーバーからの応答を待つ間にアプリケーションがハングアップすることはありません。場合によると、サーバーから返されたそのままのデータを処理するために、コレクションの parse() メソッドが使用される場合もあります (リスト 15)。

リスト 15. parse() メソッド
App.Collections.Teams = Backbone.Collection.extend({
    model : App.Models.Team,
    parse : function(data) {
        // 'data' contains the raw JSON object
        console.log(data);
    }
});

もう 1 つ、コレクションのメソッドとして興味深いものは reset() メソッドです。reset() メソッドを使用すると、いくつかのモデルを 1 つのコレクションとして設定することができます。ページをロードする場合など、ユーザーが非同期呼び出しからのリターンを待たずに済ませる上で、複数のデータをコレクションにしたい場合に reset() メソッドは非常に便利です。


ビューとクライアント・サイドのテンプレート機能

Backbone のビューは従来の MVC 手法のビューと同じではありません。Backbone のビューは Backbone.View 関数を継承し、モデルに格納されたデータを表示します。ビューが提供する HTML 要素は el 属性によって定義されます。この el 属性は tagName 属性、className 属性、id 属性の値を組み合わせて作成することも、el 属性の値を直接指定して作成することもできます。リスト 16 は異なる方法で el 属性を構成した 2 つの異なるビューを示しています。

リスト 16. Backbone のビューの例
// In the following view, el value is 'UL.team-element'
App.Views.Teams = Backbone.View.extend({
    el : 'UL.team-list'
});
// In the following view, el value is 'div.team-element'
App.Views.Team = Backbone.View.extend({
    className : '.team-element',
    tagName : 'div'
});

el 属性、tagName 属性、className 属性、そして id 属性が空の場合には、デフォルトとして空の div が el に割り当てられます。

先ほど触れたように、ビューはモデルと関連付ける必要があります。そのためには model 属性が便利です (リスト 17)。App.View.Team ビューは App.Models.Team モデルのインスタンスと関連付けられます。

リスト 17. Backbone のビューの model 属性
// In the following view, el value is 'UL.team-element'
App.Views.Team = Backbone.View.extend({
    ...
    model : new App.Models.Team
});

ビューの主目的であるデータを描画するためには、el 属性によって参照される DOM 要素内にモデルの属性を表示するロジックで render() メソッドをオーバーライドします。リスト 18 は render() メソッドによってユーザー・インターフェースを更新する例を示しています。

リスト 18. render() メソッド
App.Views.Team = Backbone.View.extend({
    className : '.team-element',
    tagName : 'div',
    model : new App.Models.Team
    render : function() {
        // Render the 'name' attribute of the model associated
        // inside the DOM element referred by 'el'
        $(this.el).html("<span>" + this.model.get("name") + "</span>");
    }
});

また Backbone は、クライアント・サイドでテンプレート機能を使用することを促すため、JavaScript の中に HTML コードを埋め込む必要がありません (リスト 18)。(テンプレート機能によって、複数のビューに共通の関数はテンプレートでカプセル化されるため、その関数は 1 度だけ指定することになります。) Backbone は underscore.js (必須ライブラリー) の中にテンプレート・エンジンを持っていますが、必ずしもこのテンプレート・エンジンを使う必要はありません。リスト 19 の例は underscore.js の HTML テンプレートを使用しています。

リスト 19. テンプレートを含む HTML
<script id="teamTemplate" type="text/template">
    <%= name %>
</script>

リスト 20 は underscore.js の HTML テンプレートを使用する別の例を示しています。

リスト 20. _.template() 関数を使用したビュー
App.Views.Team = Backbone.View.extend({
    className : '.team-element',
    tagName : 'div',
    model : new App.Models.Team
    render : function() {
        // Compile the template
        var compiledTemplate = _.template($('#teamTemplate').html());
        // Model attributes loaded into the template. Template is
        // appended to the DOM element referred by the el attribute
        $(this.el).html(compiledTemplate(this.model.toJSON()));
    }
});

Backbone のなかで最も便利で興味深い機能の 1 つは、render() メソッドをモデルの変更イベントにバインドできる機能です (リスト 21)。

リスト 21. モデルの変更イベントにバインドされた render() メソッド
// In the following view, el value is 'div.team-element'
App.Views.Team = Backbone.View.extend({
    model : new App.Models.Team,
    initialize : function() {
        this.model.bind("change", this.render, this);
    } 
})

このコードは render() メソッドをモデルの変更イベントにバインドしています。そのため、モデルが変更されると render() メソッドが自動的に実行され、コードの大幅な削減につながっています。Backbone 0.5.2 以降では、bind() メソッドはコールバック関数のオブジェクトを定義する 3 番目のパラメーターを受け付けます。(先ほどの例では、現在のビューはコールバック render() 内のオブジェクトです。) Backbone 0.5.2 よりも前のバージョンでは、underscore.js の bindAll 関数を使用する必要がありました (リスト 22)。

リスト 22. _.bindAll() の使い方
// In the following view, el value is 'div.team-element'
App.Views.Team = Backbone.View.extend({
    initialize : function() {
        _.bindAll(this, "render");
        this.model.bind("change", this.render);
    } 
})

Backbone のビューの場合、ビュー内で DOM 要素によってスローされたイベントを容易にリッスンすることができます。そのためには events 属性が非常に便利です (リスト 23)。

リスト 23. events 属性
App.Views.Team = Backbone.View.extend({
    className : '.team-element',
    tagName : 'div',
    events : {
        "click a.more" : "moreInfo"
    },
    moreInfo : function(e){
         // Logic here
    }
})

events 属性の各項目には以下の 2 つの部分があります。

  • 左側の部分は、イベントのタイプと、イベントをトリガーするセレクターを表します。
  • 左側の部分はイベント・ハンドラー関数を定義します。

リスト 23 の場合、team-element クラスの div 内にある more クラスのリンクをユーザーがクリックすると、moreInfo 関数が呼び出されます。


まとめ

MVC パターンを使用すると、大規模な JavaScript アプリケーションに必要とされる、適切な構成のコードを実現することができます。Backbone は、短期間で習得できる軽量の JavaScript フレームワークです。アプリケーションは、モデル、ビュー、コレクション、ルーターによってそれぞれに異なる層に分割され、層ごとに特有の処理が行われます。Ajax アプリケーション、つまり SPI アプリケーションを扱う上で、Backbone は適切なソリューションになる可能性があります。


ダウンロード

内容ファイル名サイズ
Article source codeIBM_Backbone.zip45KB

参考文献

学ぶために

製品や技術を入手するために

  • Backbone.js: Backbone.js をダウンロードして追跡してください。
  • underscore.js: 開発バージョンまたは本番バージョンをダウンロードしてください。
  • IBM 製品の評価版: IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、WebSphere などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, Open source
ArticleID=788485
ArticleTitle=Backbone 入門
publish-date=01272012