試して学ぶ Backbone.js入門
はじめに
Backbone.jsはJavaScript MVCフレームワークの1つです。Backbone.jsはその名が示す通り、Webアプリケーションにアーキテクチャという背骨を提供することを目的としたライブラリです。直ぐに複雑なスパゲッティコードになりがちなWebアプリケーションに、Backbone.jsが提供するEvent, Model, Collection, View, Routerというコンポーネントを使うことで、Webアプリケーションに一定の構造を与えることが可能になります。
例:Backbone.jsによるアーキテクチャイメージ
Backbone.jsによるWebアプリケーションの一例
様々なJavaScript MVCフレームワークがあるなか、Backbone.jsをオススメする理由は以下の通りです。
- 軽量で見通しがよい
サイズは6.3kb(圧縮+gzipped) - すでに数多くの利用実績がある
LinkedIn Mobile, Foursquare, Basecampなど多数 - 他のお気に入りのライブラリと組み合わせることができる
例えば、利用するViewライブラリに特に制限がない
Backbone.jsは以下のライブラリに依存しています。
- Underscore.js ( >= 1.4.3) またはLo-Dash
- json2.js
- jQuery ( >= 1.7.0)またはZepto
本記事は執筆時点での最新版である Backbone 0.9.10 を対象にしています。
セットアップ
まず始めに、Backbone.jsを動かしながら確認できるように環境をセットアップしましょう。
必要な依存ライブラリとBackbone.js、コードを書くためのjsファイルを用意します。
今回のソースの全体はこちらで確認することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <!DOCTYPE html> <html> <head> <title>Backbone Example</title> </head> <body> <h1>Backbone Example</h1> <script src= "lib/js/jquery-1.9.1.js" ></script> <script src= "lib/js/json2.js" ></script> <script src= "lib/js/underscore.js" ></script> <script src= "lib/js/backbone.js" ></script> <script src= "js/app.js" ></script> </body> </html> |
ここからはapp.jsにコードを書いて行きます。app.jsの中身は以下の通りです。
1 2 3 4 5 | ( function (){ console.log( "Hello Backbone!" ); }()); |
動作の確認をするには、上記のhtmlをブラウザで開きます。
jsの動作を確認するために、ブラウザのコンソールを使いますので、使用するブラウザに合わせて環境を作ってください。
ブラウザコンソールによる動作確認
Modelの基本
それではアプリケーションの中心となるBackbone.Model(以降 Model)から始めましょう。Modelはアプリケーション内で利用するデータそのものを表し、またデータに関わるロジックを持つことがその役割となります。
早速Modelを作成して、何ができるか確認してみましょう。
1 2 3 4 5 6 7 | var obj = new Backbone.Model(); obj.set({name: "Murata" }); obj.set({age: 20}); console.log( "obj: " + JSON.stringify(obj)); console.log( "obj.name: " + obj.get( "name" )); |
(コンソール出力結果)
1 2 | obj: { "name" : "Murata" , "age" :20} obj.name: Murata |
Moldelのインスタンスを生成するには、newを使います。setはkey-value形式でModelの属性を定義、getでkeyを指定することで対応するvalueを取得することができます。
1 | var obj2 = new Backbone.Model({name: "Kenichiro" , age: 30}); |
上記のようにコンストラクタの引数としてパラメータを渡すことで、最初から属性を設定することができます。
実際のアプリケーション開発では、Modelとしてデフォルト値や個別の関数を追加することがあるため、通常はModelを継承して、独自のModelを定義します。Modelを継承するには、以下のようにextendを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var Staff = Backbone.Model.extend({ defaults: { "name" : ", " age ": 0, " updateTime ": new Date() }, initialize: function() { console.log(" Staff[ " + this.cid + " ]: " + JSON.stringify(this)); } }); var tmpStaff = new Staff(); tmpStaff.set({name: " Murata ", age: 15, id: 101}); console.log(" Staff[ " + tmpStaff.cid + " ]: " + JSON.stringify(tmpStaff)); var tmpStaff2 = new Staff({name: " Kenichiro", age: 35, id: 102}); |
(コンソール出力結果)
1 2 3 | Staff[c3]: { "name" : "," age ":0," updateTime ":" 2013-02-07T06:06:33.887Z "} Staff[c3]: {" name ":" Murata "," age ":15," updateTime ":" 2013-02-07T06:06:33.887Z "," id ":101} Staff[c4]: {" name ":" Kenichiro "," age ":35," id ":102," updateTime ":" 2013-02-07T06:06:33.887Z"} |
defaultsはインスタンス生成時に設定するデフォルト値を定義することができます。initializeはインスタンス生成時に初期処理を行いたい場合に実装します。上記の例で分かる通り、initializeはdefaultsで指定した値、およびコンストラクタで渡されたパラメータが設定された後に呼び出されます。
id属性は、Modelが持っている属性であり、Modelの識別子として使います。クライアントーサーバ間を通してModelのインスタンスを一意に特性するものであり、通常は(データを永続化している)サーバ側にて付与します。一方、cid属性はクライアント側にて、Modelのインスタンスを生成する際に付与される識別子です。この値は異なるクライアント同士では重複することがあるため、自クライアント内でのみ使用します。
Collectionの基本
Modelの基本的な使い方が分かったら、次はBackbone.Collection(以降、Collection)です。Collectionは複数のModelを一つのリストとして保持することが役割であり、リスト化されたModelを操作するための便利な関数を多数備えています。
それでは、Collectionを生成し、何ができるか確認してみましょう。
1 2 3 4 5 | var objs = new Backbone.Collection([obj, obj2]); console.log( "objs: " + JSON.stringify(objs)); console.log( "objs.get(cid): " + JSON.stringify(objs.get( "c1" ))); console.log( "objs.at(index): " + JSON.stringify(objs.at(0))); |
(コンソール出力結果)
1 2 3 | objs: [{ "name" : "Murata" , "age" :20},{ "name" : "Kenichiro" , "age" :30}] objs.get(cid): { "name" : "Murata" , "age" :20} objs.at(index): { "name" : "Murata" , "age" :20} |
Collectionのインスタンスを生成するにはnewを使います。パラメータに追加するModelを指定することで、CollectionにModelを追加することができます。
CollectionからModelを取り出すにはgetを利用します。getにはidまたはcidを指定することができます。atはCollection内のModelの位置(index)を指定することで、Modelを取得します。後述するsortによってCollection内がsortされている場合に利用すると便利です。sortしていない場合はCollectionに追加された順となります。
1 2 3 4 5 6 7 | // add objs.add( new Backbone.Model({name: "Acroquest" , age: 20})); objs.add( new Backbone.Model({name: "Technology" , age: 10})); // length console.log( "objs.length: " + objs.length); console.log( "objs: " + JSON.stringify(objs)); |
(コンソール出力結果)
1 2 | objs.length: 4 objs: [{ "name" : "Murata" , "age" :20},{ "name" : "Kenichiro" , "age" :30},{ "name" : "Acroquest" , "age" :20},{ "name" : "Technology" , "age" :10}] |
CollectionにModelを追加するにはaddを使います。通常は末尾に追加されますが、{at: index}オプションを指定することで、指定の位置に追加することが可能です。Collectionの要素数は、length属性で確認することができます。
1 2 3 4 5 6 7 8 | // sort and comparator objs.comparator = function (item) { return item.get( "age" ); }; objs.sort(); console.log( "After sort objs: " + JSON.stringify(objs)); console.log( "After sort objs.at(index): " + JSON.stringify(objs.at(0))); |
(コンソール出力結果)
1 2 | After sort objs: [{ "name" : "Technology" , "age" :10},{ "name" : "Murata" , "age" :20},{ "name" : "Acroquest" , "age" :20},{ "name" : "Kenichiro" , "age" :30}] After sort objs.at(index): { "name" : "Technology" , "age" :10} |
Collectionをsortするには整列されるためのcomparator関数を定義する必要があります。ここではage属性の値を用いて、昇順に整列するようにしています。
1 2 3 4 | // remove objs.remove(obj2); console.log( "objs.length: " + objs.length); console.log( "objs: " + JSON.stringify(objs)); |
(コンソール出力結果)
1 2 | objs.length: 3 objs: [{ "name" : "Technology" , "age" :10},{ "name" : "Murata" , "age" :20},{ "name" : "Acroquest" , "age" :20}] |
CollectionからModelを削除するにはremoveを使います。引数には削除するModelオブジェクト、またはModelオブジェクトの配列を渡します。
Modelを継承して、独自のModelを定義したように、実際のアプリケーション開発では独自のModelを格納するためのCollectionを定義します。Collectionを継承するには、以下のようにextendを使います。
1 2 3 4 5 6 7 8 9 10 | var Staffs = Backbone.Collection.extend({ model: Staff }); var staffs = new Staffs([tmpStaff, tmpStaff2]); console.log( "staffs: " + JSON.stringify(staffs)); console.log( "staffs.get(cid): " + JSON.stringify(staffs.get( "c4" ))); console.log( "staffs.at(index): " + JSON.stringify(staffs.at(1))); console.log( "staffs.get(id): " + JSON.stringify(staffs.get(102))); |
(コンソール出力結果)
1 2 3 4 | staffs: [{ "name" : "Murata" , "age" :15, "updateTime" : "2013-02-07T06:40:31.873Z" , "id" :101},{ "name" : "Kenichiro" , "age" :35, "id" :102, "updateTime" : "2013-02-07T06:40:31.873Z" }] staffs.get(cid): { "name" : "Kenichiro" , "age" :35, "id" :102, "updateTime" : "2013-02-07T06:40:31.873Z" } staffs.at(index): { "name" : "Kenichiro" , "age" :35, "id" :102, "updateTime" : "2013-02-07T06:40:31.873Z" } staffs.get(id): { "name" : "Kenichiro" , "age" :35, "id" :102, "updateTime" : "2013-02-07T06:40:31.873Z" } |
model属性を定義して、格納するModelクラスを指定することで、後に説明するurl属性の継承などができます。
Collectionの便利な関数
それではここから、Collectionが持つ便利な関数をいくつか確認しましょう。Collectionには、Underscore.jsにて定義されている関数が多数実装されています。ここで取り上げるのは一部ですが、詳細はドキュメントを参照してください。
1 2 3 4 | // each objs.each( function (item, index){ console.log( "each(" + index + "): " + JSON.stringify(item)); }); |
(コンソール出力結果)
1 2 3 4 | each(0): { "name" : "Technology" , "age" :10} each(1): { "name" : "Murata" , "age" :20} each(2): { "name" : "Acroquest" , "age" :20} each(3): { "name" : "Kenichiro" , "age" :30} |
1 2 3 4 5 | // find var tmpObj = objs.find( function (item) { return item.get( "age" ) === 20; }); console.log( "find result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | find result: { "name" : "Murata" , "age" :20} |
1 2 3 4 5 | // filter tmpObj = objs.filter( function (item){ return item.get( "age" ) === 20; }); console.log( "filter result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | filter result: [{ "name" : "Murata" , "age" :20},{ "name" : "Acroquest" , "age" :20}] |
1 2 3 | // where tmpObj = objs.where({age: 20}); console.log( "where result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | where result: [{ "name" : "Murata" , "age" :20},{ "name" : "Acroquest" , "age" :20}] |
1 2 3 4 5 | // max tmpObj = objs.max( function (item){ return item.get( "age" ); }); console.log( "max result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | max result: { "name" : "Kenichiro" , "age" :30} |
1 2 3 4 5 | // map tmpObj = objs.map( function (item){ return item.get( "age" ) + 1; }); console.log( "map result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | map result: [11,21,21,31] |
1 2 3 4 5 | // reduce tmpObj = objs.reduce( function (memo, item){ return memo + item.get( "age" ) ; }, 0); console.log( "reduce result: " + JSON.stringify(tmpObj)); |
(コンソール出力結果)
1 | reduce result: 80 |
1 2 | // pluck console.log( "pluck result: " + JSON.stringify(objs.pluck( "name" ))); |
(コンソール出力結果)
1 | pluck result: [ "Technology" , "Murata" , "Acroquest" , "Kenichiro" ] |
このように、アプリケーションの中心となるデータのリストに対して、多数の便利な関数を利用することができます。ぜひ、ドキュメントを参照して、実際に動かして試してみてください。
次回
今回は第1回目として、ModelとCollectionの基本について説明しました。次回はModelとCollectionのRESTful JSONインタフェースによる永続化について取り上げます。
執筆者プロフィール 村田賢一郎
Acroquest Technology 株式会社勤務。Javaによるミッションクリティカルな集中監視システムのフレームワーク開発、およびライフラインを支えるシステム開発に携わる。非同期処理、メッセージング、HAなどが本業である傍ら、Webによる新しいUI表現、開発手法に興味があり、あれこれ模索している。