モバイル開発の人気が急増するなか、多くの開発者たちは種類の異なるモバイル・プラットフォームごとに同じアプリケーションを繰り返し作成し直す代わりに、モバイル Web の道を採るようになっています。けれども「Web の道を採る」ことによって諦めなければならないこともあります。その 1 つが、ネイティブ・モバイル・アプリケーション開発者の作業を楽にするアプリケーション・フレームワークです。そのことから、最近では新しい Web アプリケーション・フレームワークの数々が登場してきています。これらの新しいフレームワークのうち、この 4 回の連載では SproutCore、Cappuccino、jQTouch、Sencha Touch の 4 つを取り上げ、それぞれの機能を比較し、モバイル Web アプリケーションの構築に使用する上での利点と欠点をフレームワークごとに評価します。
この記事では、SproutCore フレームワークを使用して単純なモバイル Web アプリケーションを作成します。SproutCore はコードの生成とそのビルド・システムに Ruby と Ruby Gems をフルに活用します。この記事では使用するのは、Ruby バージョン 1.8.7 と Gems 1.3.7 です。SproutCore はすべてが JavaScript のフレームワークなので、サーバー・サイドのコンポーネントはありません。使用する HTML と CSS も必要最小限で、Web サーバーはどれを選んだとしても十分に必要を満たします。記事で使用するツールのリンクについては、「参考文献」を参照してください。
iOS デバイスの開発を拡大して、iOS のユーザーでない人々にも広めようと模索中ですか?それならば、モバイル Web と SproutCore の組み合わせが、お探しのフレームワークにぴったりかもしれません。SproutCore は、Cocoa フレームワークにヒントを得たプログラミング・モデルを、Web に適用するフレームワークです。
SproutCore は何よりもまず、Web アプリケーションのための MVC (Model-View-Controller) フレームワークです。Web 開発者であれば、Struts や Ruby on Rails などの MVC フレームワークについては十分ご存知だと思いますが、SproutCore は、Struts や Ruby on Rails とは異なります。この 2 つはサーバー・サイドのフレームワークですが、SproutCore は純粋にクライアント・サイドのフレームワークであり、M (モデル)、V (ビュー)、C (コントローラー) はいずれもクライアント・サイドに置かれます。実際、MVC が機能するには、このほうが遥かに自然な形です。実のところ、ほとんどのデスクトップ・オペレーティング・システムでは、この形がぴったりだという理由で、何十年にもわたって同様の MVC フレームワークを提供しています。
SproutCore のアーキテクチャーは、単純な MVC フレームワークの枠を超えています。このアーキテクチャーが提供するバインディング・システムのおかげで、グルー・コード (データをモデルから取得してアプリケーションのビューで使用するためのコード) を使用する必要はほとんどありません。通常、アプリケーションのコントローラー層には、グルー・コードのようなコードが必要となりますが、SproutCore Web アプリケーションのコントローラー層には一切不要です。SproutCore はさらに、データの保管および取得も抽象化します。SproutCore にはモバイル・アプリケーションに適した比較的軽量のウィジェットの数々が用意されています。開発者はこれらの SproutCore の機能を使用することで、通常の Web アプリケーションをプログラミングする場合よりも上位レベルで抽象化してプログラミングできるため、HTML 要素を作成してアクセスする必要も、CSS スタイルを管理する必要も、あるいはリモート・サーバーに XMLHttpRequest を送信する必要もありません。JavaScript を使用するという点を除けば、デスクトップ・アプリケーションやネイティブ・モバイル・アプリケーションを開発する場合と非常に良く似たプログラミング・モデルで作業することができます。
実行時の SproutCore アプリケーションは、一般に 1 つの JavaScript ファイル、1 つの CSS ファイル、そして 1 つの HTML ページだけで構成されますが、最初はいくつかの JavaScript ファイルとして存在します。これらの JavaScript ファイルが、任意の静的 Web サーバーにデプロイできるように最適化されたファイルにコンパイルされるというわけです。SproutCore にはビルド・ツールを含め、豊富なツールが用意されています。ツールが機能するためには、ソース・コードの構造を認識しなければなりません。そのために SproutCore は「Convention over Configuration (設定より規約)」の原則に依存し、プロジェクト構造、そして通常開発することになるファイル (モデルやコントローラーなど) を生成するためのツールを組み込んでいます。これらのツールは Ruby で作成されており、よく使われている Ruby on Rails フレームワークのいくつかのツールとほとんど同じように機能します。ただし、SproutCore は Pure JavaScript です。SproutCore にはサーバー・サイドのコンポーネントがないことから、サーバー上に Ruby は必要ありません。ツールを使用するには、開発マシンに Ruby をインストールするだけで済みます。
この記事の焦点は、SproutCore のツールの使用方法を説明することではなく、SproutCore アプリケーションがどのような動作をするのか詳しく調べることです。ツールの使用方法については、SproutCore に関する適切なチュートリアルがあるので、それを参考にしてください。この記事では SproutCore アプリケーションについて詳しく探るために、SproutCoreを使って開発できる典型的なモバイル Web アプリケーションについて見ていきます。これから開発するアプリケーションは、モバイル機器からアクセスするように設計された、従業員の連絡先情報のディレクトリーを提供するアプリケーションです。アプリケーションの概要として、まずはデータ・アクセス層について説明します。
SproutCore 開発は大抵の場合、ボトムアップ方式を採用して、アプリケーションに必要なデータ・モデルと、サーバーからデータにアクセスする方法を記述するところから始めます。JavaScript オブジェクトには宣言型の情報がないため、形式上でもモデルを宣言するのは直観に反しているように思えるかもしれません。けれども、モデルを宣言する利点は明らかです。リスト 1 に、このアプリケーションのデータ・モデルを記載します。
リスト 1. 従業員データ・モデル
Intradir.Employee = SC.Record.extend({
firstName: SC.Record.attr(String, { isRequired: YES }),
lastName: SC.Record.attr(String, { isRequired: YES }),
phone: SC.Record.attr(String),
email: SC.Record.attr(String),
fullTime: SC.Record.attr(Boolean, { defaultValue: YES }),
fullName: function() {
return this.getEach('firstName', 'lastName').compact().join(' ');
}.property('firstName', 'lastName').cacheable()
}) ;
|
リスト 1 のコードには、従業員データ・モデルが定義されています。アプリケーションの名前は Intradir で、オブジェクトのスコープはこのアプリケーションに設定されています。SproutCore モデルでは、レコードがよく知られており、通常は SC.Record という SproutCore のクラスを継承します。レコードには、プロパティーと型を宣言してください。リスト 1 には、firstName、lastName、phone、email、fullTime という単純な 5 つのプロパティーがあり、最初の 4 つのプロパティーの型はストリング、最後のプロパティーの型はブール値です。型を宣言するには、SC.Record.attr ヘルパー関数を使用します。このヘルパー関数は、渡された型 (ストリング、ブール、数値) に応じた SC.RecordAttribute のインスタンスを返すので、このヘルパー関数に型を渡すことは必須です。さらに、型と一緒にオプションの属性を渡すこともできます。リスト 1 では、isRequired オプションによって firstName および lastName の要件がトリガーされます。また、fullTime プロパティーには defaultValue オプションによってトリガーされるデフォルト値があります。
5 つの単純なプロパティーに続き、fullName プロパティーを宣言し、このプロパティーに 1 つの関数を指定していることに注目してください。このような計算プロパティーは、SproutCore では非常に重要です。上記の関数は、firstName および lastName プロパティーを取得し、この 2 つのプロパティーを配列のような構造に組み込んだ後、この構造を join 関数によってストリングに変換します。これは関数宣言の範囲ですが、それに加え、この計算プロパティーが firstName および lastName プロパティーに依存すること、そしてキャッシュ可能であることを宣言します。こうすることにより、SproutCore が初めてプロパティーを計算したときに、その値をキャッシュに入れ、それ以降は firstName と lastName の値が変わっていないことを認識している限り、キャッシュに入れた値を使用することができます。このような最適化によって、モバイル機器で SproutCore が発揮する非常に優れたパフォーマンスが実現されるのです。
軽量のオブジェクト・リレーショナル・マッピング (ORM) 技術の基礎となっているのは、SproutCore の Record API です。複数の型のレコードが互いに関連する場合には、1 対多、1 対 1、さらに多対多の関係を定義することもできます。レコードを作成、保管、または取得するには、SproutCore の Datastore API が必要です。アプリケーションのデータ・ストアは SproutCore が自動的に作成してくれるので、開発者はレコードを定義し、データ・ストアのデータ・ソースを定義するだけで済みます (リスト 2 を参照)。
リスト 2. 従業員データ・ソース
Intradir.EmployeesDataSource = SC.DataSource.extend({
// ..........................................................
// QUERY SUPPORT
//
fetch: function(store, query) {
if (query == Intradir.EMPLOYEE_QUERY_ALL){
SC.Request.getUrl('/app/employees.json')
.set('isJSON',YES)
.notify(this, this._didFetchAllEmployees, {
query: query,
store: store
}).send();
return YES;
}
return NO;
},
_didFetchAllEmployees: function(response, params){
if (SC.$ok(response)){
store.loadRecords(Intradir.Employee, response.get('body'));
store.dataSourceDidFetchQuery(query);
} else {
store.dataSourceDidErrorQuery(query, response);
}
},
// ..........................................................
// RECORD SUPPORT
//
retrieveRecord: function(store, storeKey) {
// Not supported
return NO ; // return YES if you handled the storeKey
},
createRecord: function(store, storeKey) {
// Not supported
return NO ; // return YES if you handled the storeKey
},
updateRecord: function(store, storeKey) {
// Not supported
return NO ; // return YES if you handled the storeKey
},
destroyRecord: function(store, storeKey) {
// Not supported
return NO ; // return YES if you handled the storeKey
}
}) ;
|
データ・ソースを作成する最も簡単な手段として使えるのは、SproutCore のコード生成ツールです。このツールで SC.DataSource を継承するオブジェクトを生成した後、5 つの関数、fetch、retrieveRecord、createRecord、updateRecord、destroyRecord を実装します。リスト 2 の例で実装されている fetch 関数は、何らかのクエリーが実行されると必ず呼び出されます。呼び出された fetch 関数は特定のクエリーを探します。リスト 3 に、この特定のクエリーとその使用方法を示します。
リスト 3. SproutCore クエリーの例
// Declare this in core.js so it can be used anywhere
Intradir.EMPLOYEE_QUERY_ALL = SC.Query.local(Intradir.Employee);
Intradir = SC.Application.create({
NAMESPACE: 'Intradir',
VERSION: '0.1.0',
// Create the store, and point it at our datasource
store: SC.Store.create().from('Intradir.EmployeesDataSource') }) ;
// Now in your application you can use the query
var directory = Intradir.find(Intradir.EMPLOYEE_QUERY_ALL);
|
リスト 3 のサンプル・コードに示されているのは、SproutCore がサポートするクエリーのうち、最も単純なクエリーの一例です。このようなローカル・クエリーは、データ・ストアにローカルに保管されている内容に対して実行されるにすぎません。けれどもリスト 2 では、データ・ストアはサーバーからデータをロードしています。サーバーからデータをロードするためには、SproutCore の Ajax ユーティリティーを使用します。Ajax ユーティリティーの呼び出しには裏で XMLHttpRequest が使用されるため、この呼び出しは非同期です。したがって、コールバック関数 _didFetchAllEmployees は、データがサーバーから返された時点で呼び出されます。この関数には任意の名前を付けられますが、ここでは SproutCore の命名規則に従うことにしました。SproutCore の命名規則では、関数の先頭に private 関数であることを示すアンダーバーを追加し、Cocoa 式の名前 (didDoWhateverWeSaidWouldDo) を使用します。
他のクエリーをサポートする必要がある場合には、必要なロジックを fetch 関数に追加してください。サポートできるクエリーには、サーバーから直接クエリーの結果を取得するリモート・クエリーも含まれます。リモート・クエリーをサポートするには一般に、リクエスト先の URL をサーバーから動的に組み立て、そして通常は結果を処理するためのコールバック関数を追加する必要があります。SproutCore データ・ソース API は個々のレコードでの操作もサポートしますが、この例ではリモート・クエリーも、個々のレコードでの操作も実装していません。これらの操作を実装するかどうかは、バックエンドがどのように設計されているかによって決まります。このアプリケーションの場合に必要なのは、データを表示し、そのデータを操作することだけです。しかし、この単純な必要を満たすには、まずはデータを取り込むためのユーザー・インターフェースを作成する必要があります。
JavaScript でのビュー、コントローラー、Key-Value オブザーバー
SproutCore でのビュー・コードは、JavaScript で宣言型のスタイルを使用して作成します。これは多くの点で HTML と似ていますが、大きく異なる点は、SproutCore にはイベント・リスナー (通称「オブザーバー」) に接続するために、HTML の場合よりも上位レベルのコンポーネントが用意されており、そのコンポーネントにデータを接続しさえすれば済むという点です。このアプリケーションの場合には、従業員データが含まれる単純なテーブルをロードし、どの列を基準にしたソートにも対応できるテーブルにします。リスト 4 にビュー・コードを記載します。
リスト 4. テーブルのビューの作成
Intradir.nameColumn = SC.TableColumn.create({
key: 'fullName',
label: 'Name',
width: 50
});
Intradir.emailColumn = SC.TableColumn.create({
key: 'email',
label: 'Email',
width: 50
});
Intradir.phoneColumn = SC.TableColumn.create({
key: 'phone',
label: 'Phone',
width: 50
});
Intradir.mainPage = SC.Page.design({
mainPane: SC.MainPane.design({
childViews: 'tableView'.w(),
tableView: SC.TableView.design({
layout: { left: 15, right: 15, top: 15, bottom: 15 },
backgroundColor: "white",
columns: [
Intradir.nameColumn,
Intradir.emailColumn,
Intradir.phoneColumn
],
contentBinding: 'Intradir.directoryController.arrangedObjects',
selectionBinding: 'Intradir.directoryController.selection',
selectOnMouseDown: YES,
exampleView: SC.TableRowView,
recordType: Intradir.Employee,
nameColumn: Intradir.nameColumn,
emailColumn: Intradir.emailColumn,
phoneColumn: Intradir.phoneColumn
})
})
});
|
上記のコードは、/resources/main_page.js ファイルに含まれています。このコードを呼び出すことはできますが、このコードに関しては JSON データであるかのように考えてください。ビュー・スクリプトで使用する必須のコード (関数) を最小限に減らすため、作成されるテーブルと directoryController の一部となっているデータ構造との間でバインディングを宣言していることに注目してください。directoryController は、このメイン・ページをサポートするコントローラーです。リスト 5 に、このコントローラーのコードを記載します。
リスト 5. メイン・ページのコントローラー
Intradir.directoryController = SC.ArrayController.create({
// nothing to see here!
}) ;
|
リスト 5 に記載したコントローラーは極めて単純なものです。このコントローラーで最も重要なことは、これは配列コントローラーであり、SC.ArrayController を継承するということです。リスト 4 で宣言したバインディングに話を戻すと、バインディングで参照されている arrangedObjects および selection プロパティーは、SC.ArrayController に定義されています。この一般的に使用されるコントローラーは、その名前が示唆するように、ビューに必要なモデル・データを配列に格納します。開発者に必要となる作業は、このコントローラーにデータを提供して、ソート処理を行うことだけです。このステップを行うアプリケーション初期化コードをリスト 6 に示します。
リスト 6. アプリケーションの初期化コード
Intradir.main = function main() {
Intradir.getPath('mainPage.mainPane').append();
var directory = Intradir.find(Intradir.EMPLOYEE_QUERY_ALL);
var dirController = Intradir.directoryController;
dirController.set('content', directory);
var tableView = Intradir.getPath('mainPage.mainPane.tableView');
// helper function
function handleSort(key, column){
var content = controller.get('content').sortProperty(key);
if (column.get('sortState') === SC.SORT_DESCENDING){
content = content.reverse();
}
dirController.set('content', content);
tableView.set('content',content);
tableView.displayDidChange();
tableView.awake();
}
// add observers
tableView.nameColumn.addObserver('sortState', function(){
handleSort("fullName", tableView.nameColumn);
});
tableView.emailColumn.addObserver('sortState', function(){
handleSort("email", tableView.emailColumn);
});
tableView.phoneColumn.addObserver('sortState', function(){
handleSort("phone", tableView.phoneColumn);
});
};
function main() { Intradir.main(); }
|
初期化の後、データ・ストアからのデータをコントローラーの中に取り込むことで、コントローラーには必要なデータの配列が用意されることになります。テーブルを表示することだけが目的の場合、コードの残りの部分でソートの処理を行うため、作業はこれで完了です。そうでない場合は、テーブルの各列を基準としたソートの処理を行うために使用できるヘルパー関数を定義し、最後に 3 つの列のそれぞれにオブザーバーを追加してください。各オブザーバーが sortState イベントを監視し、ヘルパー関数を使ってデータをソートした後、UI を最新の表示に更新します。このようなスタイルのイベント処理は、KVO (Key-Value Observing) と呼ばれています。Cocoa フレームワークでは、このパラダイムを多用することから、SproutCore による JavaScript および Web 開発の世界にも、このパラダイムが導入されています。
SproutCore の Web サイトでは、「どのようにして非常に高速なデスクトップ・クラスの Web アプリケーションを構築するのか」という質問に対して、SproutCore を使うことをその答えとしています。けれどもこの質問は、モバイル Web アプリケーションについて言及していないだけでなく、デスクトップ・クラスの Web アプリケーションであることが強調されています。これは、SproutCore が対象としているのはデスクトップ Web ブラウザーからのみアクセスできるように設計された Web アプリケーションだけであるということなのでしょうか。この質問に対する決定的な答えとして、Hedwig について考えてみてください。これは、タッチ操作に対応した機器のために設計された Web アプリケーションに SproutCore が使用されている一例です。
Hedwig の例は、モバイル・アプリケーションに SproutCore を使用する上での利点と欠点の両方を明らかにしています。まず、iPad のように大きなタッチ・スクリーンを持つ機器では、Hedwig は最も適切に表示されます。また、SproutCore はタッチ・イベントの処理、向きの変更、動的にサイズ変更されるコントロールなど、モバイル Web 開発には欠かせない多くの側面を支援することができます。その一方、iPhone や Android フォンなど、画面の小さな機器で Hedwig を表示すると、機能しない部分もあることに気付くはずです。図 1 に、Hedwig の iPad での表示と iPhone での表示を示します。
図 1. 横向きの iPad および iPhone での Hedwig
問題は、主としてレイアウト機能にあります。このページは大きな画面用に設計されているため、モバイル機器の画面では、どうしてもコントロールの一部が画面の外にはみだしてしまうからです。しかも、ページのサイズを固定するために、モバイルに適した機能としてビューポートを使用しています。つまり、コントロールがモバイル機器の画面から外れていても、ズームアウトとしてそのコントロールにアクセスすることができません。けれども、このようなページをサイズの小さいタッチ・スクリーンに対応させるには、最小限の作業だけで済む場合もあります。
モバイル開発に SproutCore を使用することを検討する場合には、SproutCore の UI コンポーネントの多くは、現在小さな画面用に設計されていないことを念頭に置く必要があります。これらのコンポーネントのサイズまたはレイアウト (あるいはその両方) は、モバイル画面には最適でない可能性があるので、コンポーネントのサイズ (つまり、どれだけの JavaScript と CSS が必要になるか)、そしてメモリーおよび CPU の使用量を考慮してください。幸い、SproutCore はレンダリング速度という点では極めて最適化されているため、モバイル機器のプロセッサーの速度が遅くても、開発者をそれほど苦しめることはありません。そうは言っても、この例で使用した SC.TableView のように大きく複雑なコンポーネントを使用する際には慎重に事を進める必要があります。SproutCore には、カスタム・コンポーネントによってレンダリングされる HTML そのものを生成するためのヘルパー・メソッドが組み込まれていることを忘れないでください。
この SproutCore について紹介する記事では、SproutCore というフレームワークそのものについてと、モバイル Web アプリケーションに SproutCore を使用する方法に重点を置きました。一方で、JavaScript をプログラミング言語として駆使する SproutCore は、Web アプリケーションを作成する際にリッチなクライアント・サイドの MVC フレームワークとなります。SproutCore はバインディングを使用してボイラープレート・コードを大幅に減らすだけでなく、Ajax をベースとした抽象化によって、クライアント上にすべての UI を作成し、サーバーにはデータを取得するためだけにアクセスするというアーキテクチャーを促進します。このアーキテクチャーでは、アプリケーション・キャッシュなどの HTML5 技術を使用することができ、モバイル Web アプリケーションにまさに最適です。SproutCore には、タッチ・イベントの抽象化も組み込まれているため、モバイル機器での UI の対話性を今までより遥かに向上させることができます。その一方、SproutCore はモバイル機器用に最適化されてはいません。UI コンポーネントのすべてがモバイル機器に適しているというわけではないので、スマートフォンで有効に機能する多数のカスタム UI コンポーネントを作成しなければならないことも考えられます。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Intradir sample code | intradir.zip | 7KB | HTTP |
学ぶために
- 公式な SproutCore 資料で、SproutCore についての詳細を熟読してください。
- モバイル Web アプリケーションで Ajax を使用する方法については、「モバイル Web 用の Ajax アプリケーションを作成する」(Michael Galpin 著、developerWorks、2010年5月) で詳しく説明しています。
- モバイル Web アプリケーションに HTML5 を使用する方法について学ぶには、この 5 回連載の developerWorks 記事、「HTML 5 を使ってモバイル Web アプリケーションを作成する」(Michael Galpin 著、developerWorks、2010年6月) を読んでください。
- 「Android と iPhone のブラウザー戦争: 第 1 回 WebKit による救いの手」(Frank Ableson, developerWorks, 2009年12月) を読んで、モバイル・ブラウザーの機能を利用する方法を調べてください。
- Android SDK を使用して XML を構文解析するさまざまな方法について詳しく学ぶには、「Android で XML を扱う」(Michael Galpin 著、developerWorks、2009年6月) を読んでください。
- 「HTML 5 の新要素」(Elliotte Rusty Harold 著、developerWorks、2007年8月) を読むと、HTML 5 では JavaScript がすべてではないことがわかります。
- Android を学び始めるには、「Android 開発入門」(Frank Ableson 著、developerWorks、2009月5月) が絶好の出発点となります。
- developerWorks Web development ゾーンでは、多種多様な Web ベースのソリューションを話題にした記事を揃えています。
製品や技術を入手するために
- SproutCore をインストールするのに最も簡単な方法は、RubyGem としてインストールすることです。
- Ruby をダウンロードしてください。この記事ではバージョン 1.8.7 を使用しました。
- RubyGems をダウンロードしてください。この記事ではバージョン 1.3.7 を使用しました。
- IBM 製品の評価版をダウンロードして、DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を使ってみてください。
議論するために
- 今すぐ My developerWorks で自分のプロフィールを作って、モバイル Web アプリケーションに関するウォッチ・リストをセットアップしてください。My developerWorks とずっとつながっていられます。
- Web 開発に興味を持つ他の developerWorks メンバーを見つけてください。
- Web のトピックを専門とする developerWorks グループに参加して、知識を共有してください。
- Roland Barcia が彼のブログで Web 2.0 とミドルウェアについて語っています。
- developerWorks のメンバーが共有する Web 関連のブックマークをフォローしてください。
- Web 2.0 Apps フォーラムで、素早く回答を得てください。

Michael Galpin は eBay のアーキテクトであり、developerWorks に頻繁に寄稿しています。彼は JavaOne、EclipseCon、AjaxWorld など、さまざまな技術カンファレンスで講演を行っています。彼の次のプロジェクトを知るには、Twitter で @michaelg をフォローしてください。