クライアント・サイドのスクリプトや 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 を描画し、ビューの構成要素上でトリガーされたイベントを処理します。
この記事では、Backbone.js フレームワークのさまざまなコンポーネントについて説明し、Backbone に MVC がどのように適用されるかを詳しく探ります。また、Ajax アプリケーションや SPI (Single Page Interface: シングル・ページ・インターフェース) を作成する際に、Backbone がいかに有効であるかをさまざまな例をとおして示します。
この記事で使用するソース・コードをダウンロードしてください。
SPI アプリケーション: Backbone.Router と Backbone.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 つのアクション (index、getTeams、getTeamsCountry、getTeamCountry、fourOfour) は、それぞれ下記タイプの 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は、パラメーターとしてcountry1とteam1を渡して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
に、イベント・ハンドラーを変更イベントにバインドする方法を示します。このイベント・ハンドラーにはアラートが含まれており、このアラートはリスト 6 の set() メソッドが呼び出されると実行されますが、リスト 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 リクエスト (POST、GET、PUT、DELETE)
と関連付けられます。モデル・オブジェクトが初めて保存される場合には 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 code | IBM_Backbone.zip | 45KB | HTTP |
学ぶために
- Getting
Started with Backbone.js: この簡潔なチュートリアルは Backbone.js を紹介しています。
- Backbone.js のドキュメント: イベント、モデル、コレクション、ルーター、同期、ビュー、その他 Backbone.js の要素についての資料を読んでください。
- Underscore.js: JavaScript のためのユーティリティー・ライブラリーについての資料を読んでください。
- サンプル: Backbone の最小限のビューを宣言してインスタンス化する方法の例を見てください。
- 「Test-Driving
Backbone Views With JQuery Templates, The Jasmine Gem, and Jasmine-JQuery」(Derick Bailey 著、2011年9月): Backbone のビューをテストする方法についての記事を読んでください。
- SPI マニフェスト: シングル・ページ・インターフェースについて学んでください。
- Model View Controller: ウィキペディアで MVC ソフトウェア・アーキテクチャーの概要について学んでください。
- W3C の DOM レベル 2
コア仕様: Document Object Model のコア仕様を読んでください。
- 「Understanding DOM」(Nicholas Chase 著、developerWorks、2007年3月): DOM 文書の構造について学んでください。
- Using
the Document Object Model from JavaScript: JavaScript DOM の使い方についての資料を読んでください。
- W3C の DOM レベル 3
イベント仕様: Document Object Model のレベル 3 イベント仕様を読んでください。
- developerWorks の XML ゾーン:
DTD、スキーマ、XSLT など、XML の領域でのスキルを磨くためのリソースが豊富に用意されています。XML 技術文書一覧に用意された、さまざまな技術記事やヒント、チュートリアル、技術標準、IBM Redbooks を見てください。
- IBM XML certification: XML および関連技術において IBM 認定技術者になる方法を参照してください。
- developerWorks の Technical events and webcasts: これらのセッションで最新情報を入手してください。
- developerWorks on Twitter: 今すぐ Twitter に参加して developerWorks のツイートをフォローしてください。
- developerWorks
podcasts: ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
- developerWorks On demand demos: 初心者のための製品インストール方法やセットアップのデモから、上級開発者のための高度な機能に至るまで、多様な話題が解説されています。
製品や技術を入手するために
- Backbone.js: Backbone.js をダウンロードして追跡してください。
- underscore.js: 開発バージョンまたは本番バージョンをダウンロードしてください。
- IBM 製品の評価版: IBM
製品の評価版をダウンロードするか、あるいは IBM SOA
Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、WebSphere などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
議論するために
- developerWorks
プロフィール: 今すぐプロフィールを作成し、ウォッチ・リストをセットアップしてください。
- XML
ゾーンのディスカッション・フォーラム: いくつかのフォーラムで XML 関連の議論が行われています。いずれかのフォーラムで議論に参加してください。
- developerWorks コミュニティー: 開発者向けのブログ、フォーラム、グループ、ウィキなどを利用しながら、他の developerWorks ユーザーとやり取りしてください。

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