レベル: 中級 Michael Galpin (mike.sr@gmail.com), Software architect, Ludi Labs
2008年 6月 24日 皆さんは Web アプリケーションを開発中でしょうか。その Web アプリケーションは cragislist や flickr とよく似たものでなければならないのでしょうか。答えが前者なら、おそらくこの記事を読み飛ばすことができます。もしまだこの記事を読み続けているなら、皆さんは幸運です。JavaScript ライブラリーに関する 3 回シリーズの第 2 回である今回は、Scriptaculous JavaScript ライブラリーを使って Web アプリケーションの機能を強化するための方法を学びます。
この記事は、Ajax で強化したアプリケーションを作成するためによく使われる JavaScript ライブラリーに関する 3 回シリーズの第 2 回です。「第 1 回」では Prototype ライブラリーについて学び、曲を管理するための Web アプリケーションを作成しました。この記事では Scriptaculous ライブラリーを使って、写真を管理するための Web アプリケーションを作成する方法を学びます。
この記事では Scriptaculous の最新バージョンである、バージョン 1.8.1 を使います (「参考文献」にリンクがあります)。Scriptaculous は Prototype 1.6 ライブラリーを使っています。この記事を読むためには JavaScript、HTML、CSS について理解している必要があります。この記事では Scriptaculous を Ajax で使う方法について説明します。バック・エンドとしては Ruby on Rails 2.0 と MySQL 5.0.4 とを組み合わせて使います (「参考文献」を参照)。もちろんバック・エンド技術は、わずかな調整を行うだけで、お好みのものに置き換えることができます。
Scriptaculous の紹介
Scriptaculous JavaScript ライブラリーは入手可能なライブラリーの中で最も人気の高いものの 1 つです。このライブラリーは HTML ベースの Web サイトにリッチな対話動作を追加するために使われます。またこのライブラリーは非常に多くのビジュアル・エフェクトと振る舞いを備えているため、それらの中から自由に選択して Web アプリケーションに対話動作を追加することができます。Scriptaculous は Prototype ライブラリーの上に構築されています。
図 1. Scriptaculous と Prototype のスタック
第 1 回を読んだ人は、Prototype によって Ajax が抽象化される例を見たはずです。Scriptaculous は Prototype と似たような独自の機能を作成する代わりに、Prototype を使用し、Prototype の上にビジュアル・エフェクトと振る舞いを追加しています。Scriptaculous は、ドラッグ・アンド・ドロップ要素など、リッチなコントロールを数多く提供しています。またそうしたコントロールと組み合わせて使用できる、驚くほど素晴らしいビジュアル・エフェクトも提供しています。
ドラッグ・アンド・ドロップ・コントロール
アプリケーションに追加できる機能として最も便利で、非常に視覚的でわかりやすい機能は、ドラッグ・アンド・ドロップです。ドラッグ・アンド・ドロップ機能はデスクトップ・アプリケーションでは当たり前の機能と思われるようになりましたが、Web アプリケーションにはそれほど頻繁に見られるものではありません。Web アプリケーションにドラッグ・アンド・ドロップ機能を追加することで、リッチなユーザー・エクスペリエンスを提供することができます。これは膨大な作業に思えるかもしれませんが、Scriptaculous を使用すれば驚くほど簡単なのです。それを示すために、ここではサンプル・アプリケーションを取り上げ、それを分析しながら、Scriptaculous を使用するメリットを説明していきます。
サンプル・アプリケーション: 写真整理ツール (photo organizer)
ここではサンプル・アプリケーションとして、写真を管理するためのアプリケーションを作成します。このアプリケーションでは、あるセットへの写真の追加や削除を行うことができ、しかもそうしたことをすべてドラッグ・アンド・ドロップ・メタファーを使って行うことができます。バック・エンドを作成するためには Ruby on Rails と MySQL を使います。実は Ruby on Rails にはデフォルトで Scriptaculous が含まれており、アプリケーションの中で自動的に Scriptaculous を使用できる API が提供されています。ここでは Rails のこうした機能を使わず、単にデータ・サービスのバック・エンドを提供するために Rails を使用します。
写真整理ツールのバック・エンド
バック・エンドの説明にはあまり時間をかけません。バック・エンドについては、フロント・エンドの動作を理解するのに困らない程度に把握できていれば十分です。すべてのコードは、この記事の「ダウンロード」セクションに用意されています。まず、Rails の pix を使って Rails アプリケーションを作成します。次に、Rails の命名規則に従って、pix_development というデータベースを作成します。データベースに対する構成パラメーター (ホスト、名前、パスワード) を変更するためには config/database.yml ファイルを変更する必要があります。
最も重要なステップが、次のステップ、つまりアプリケーションに対する scaffold を作成するステップです。通常は単に作業を開始するために scaffold を作成しますが、ここでは Ajax を使って呼び出せる RESTful なインターフェースを作成するために scaffold を使います。そのためのコマンドは、ruby script/generate scaffold photo thumb :string caption :string inSet :boolean です。このコマンドによって、(サムネイル画像用の) サム・フィールドと見出し、そしてその写真がこの記事での仮想セットの中にあるかどうかを示すブール値を持つモデルが作成されます。次に、rake db:migrate コマンドによって、データベースの中に必要なテーブルが作成されます。また scaffold 生成コマンドによって、このアプリケーション用のコントローラーも作成されます。このコントローラーは、Rails が自動的に作成する RESTful なサービスの JSON 版を提供するように変更する必要があります。この変更されたコントローラーをリスト 1 に示します。
リスト 1. 写真用 Web コントローラー
class PhotosController < ApplicationController
# GET /photos
# GET /photos.xml
protect_from_forgery :only => [:create]
def index
@photos = Photo.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @photos }
format.json { render :json => @photos }
end
end
# GET /photos/1
# GET /photos/1.xml
def show
@photo = Photo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/new
# GET /photos/new.xml
def new
@photo = Photo.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @photo }
format.json { render :json => @photo }
end
end
# GET /photos/1/edit
def edit
@photo = Photo.find(params[:id])
end
# POST /photos
# POST /photos.xml
def create
@photo = Photo.new(params[:photo])
respond_to do |format|
if @photo.save
flash[:notice] = 'Photo was successfully created.'
format.html { redirect_to(@photo) }
format.xml { render :xml => @photo, :status => :created,
:location => @photo }
format.json { render :json => @photo, :status => :created,
:location => @photo }
else
format.html { render :action => "new" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# PUT /photos/1
# PUT /photos/1.xml
def update
@photo = Photo.find(params[:id])
respond_to do |format|
if @photo.update_attributes(params[:photo])
flash[:notice] = 'Photo was successfully updated.'
format.html { redirect_to(@photo) }
format.xml { head :ok }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @photo.errors, :status =>
:unprocessable_entity }
format.json { render :json => @photo.errors, :status =>
:unprocessable_entity }
end
end
end
# DELETE /photos/1
# DELETE /photos/1.xml
def destroy
@photo = Photo.find(params[:id])
@photo.destroy
respond_to do |format|
format.html { redirect_to(photos_url) }
format.xml { head :ok }
format.json { head :ok }
end
end
end
|
ここで、いくつかの要素に注目してください。まず、protect_from_forgery コマンドによって、create 操作以外のすべての操作が Web サービスとして利用できるようになります。次に、format.json { render :json ... } を追加した respond_to do |format| ブロックの中に注目してください。このブロックは RoR に組み込まれた JSON サポートを利用して、.json で終わる URL に応答します。つまり /photos.json が index メソッドを呼び出す際には、すべてのデータを JSON としてシリアライズした形で渡すことになります。このシリーズの「第 1 回」で見たように、これは Prototype にデータを渡すための方法として非常に便利です。これで、バック・エンドでデータがどのように見えるのか、またどのようにデータが提供されるかがわかります。今度はフロント・エンドと、フロント・エンドで Scriptaculous がどのように使われているかを調べます。
写真整理ツールのフロント・エンド
フロント・エンドを理解するために、コードを調べ、その動作を検証してみましょう。フロント・エンドは単純な HTML ページです (リスト 2)。
リスト 2. organize.html の Web ページ
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Photo Organizer</title>
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/effects.js"></script>
<script type="text/javascript" src="javascripts/dragdrop.js"></script>
<script type="text/javascript" src="javascripts/builder.js"></script>
<script type="text/javascript" src="javascripts/organizer.js"></script>
<script type="text/javascript" src="javascripts/organize_page.js"></script>
<link rel="stylesheet" href="stylesheets/organizer.css"/>
</head>
<body onLoad="initPage()">
<div id="pageTitle">Organize Your Photos</div>
<div id="allPics">
<span id="setPics"></span>
<span id="availablePics"></span>
</div>
<div id="trash"> </div>
</body>
</html>
|
このコードは非常に単純に見えるはずです。JavaScript ファイルがいくつかロードされていることに注目してください。prototype.js をロードしなければならない理由は、このファイルを Scriptaculous が (そして私達も) 使うからです。次に、Scriptaculous の一部である 3 つのライブラリーを使用しています (effects.js と dragdrop.js、そして builder.js)。Scriptaculous はかなりモジュール化が進められているため、Scriptaculous のモジュールのうち、必要なものだけを使用することができます。しかし dragdrop.js は effects.js との間に依存関係があるため、(ドラッグ・アンド・ドロップを使う場合には) 必ず両方を一緒に含める必要があります。もちろん、最も興味深いコードは、このアプリケーション専用の 2 つの JavaScript ファイルの中にあります。このページはロードされると、organize_page.js の中にある initPage 関数を呼び出します (リスト 3)。
リスト 3. ページを初期化する JavaScript
// the mode for the page
var model = {};
// page initialization
function initPage(){
var options = {
method : "get",
onSuccess : initUi
}
new Ajax.Request("photos.json",options);
}
function initUi(xhr){
model = new Organizer(xhr.responseJSON);
var list = Builder.node("span", {id: "inList"});
var list2 = Builder.node("span", {id: "outList"});
var pix = model.pixList;
for (var i=0; i < pix.length; i++){
var pic = buildPicUi(pix[i]);
if (pix[i].inSet){
list.appendChild(pic);
} else {
list2.appendChild(pic);
}
}
$("setPics").appendChild(list);
$("availablePics").appendChild(list2);
// setup drag-and-drop
makeDraggables();
initDropZones();
}
|
最初の関数、initPage は、これまで Prototype を使ったことのある人にはおなじみのはずです。initUi 関数は単純に、JSON として描画される写真のリストをサーバーに対して要求します。initPage 関数は、サーバーからの応答を処理するために呼び出す関数として initUi 関数を設定します。次にこの initUi 関数を見てみましょう。
initUi 関数はサーバーからデータを取得し、Organizer オブジェクトを作成します。このオブジェクトは別の JavaScript ファイルの中に含まれ、このアプリケーションに対するモデルとして動作します。このオブジェクトのコードについては、この記事のソース・コードに含まれているので、それを見てもらうことにし、ここでは UI コンポーネントについて説明することにします。ここでは、写真に関する 2 つのグループ (つまりセットの「中に入っている」写真のグループと、入っていない写真のグループ) に対して、視覚コンポーネントを動的に作成します。そのために Scriptaculous の Builder ライブラリーを使って、DOM 要素を動的に作成します。次に buildPicUi 関数を使って、モデルの中の写真に対する視覚表現を繰り返しの処理により作成します。この関数をリスト 4 に示します。
リスト 4. buildPicUi JavaScript 関数
function buildPicUi(pic){
var picId = "pic" + pic.id;
var img = Builder.node("img", {src : "images/"+pic.thumb, alt: pic.caption});
var picNode = Builder.node("div",{id : picId, class: "pic"},img);
return picNode;
}
|
この場合も Builder ライブラリーを利用して DOM 要素を容易に作成します。ここでは div の中に画像 (img タグ) を作成します。ここで initUi 関数に戻ると、他の 2 つの関数 (makeDraggables と initDropZones) を呼び出していることがわかります。この 2 つの関数をリスト 5 に示します。
リスト 5. ドラッグ・アンド・ドロップを実現する
function makeDraggables(){
for (var picId in model.pixMap){
new Draggable(picId, {revert:true});
}
}
function initDropZones(){
Droppables.add("setPics",{onDrop: addToSet, accept: "pic"});
Droppables.add("availablePics",{onDrop: removeFromSet, accept: "pic"});
Droppables.add("trash",{onDrop: addToTrash, accept: "pic"});
}
|
この 2 つの関数によって、アプリケーションの中でのドラッグ・アンド・ドロップ機能をすべて実現することができます。makeDraggables 関数は単純にすべての写真に対して繰り返しの処理により各写真の Draggable オブジェクトを作成します。必要なものは、その写真の DOM ID (リスト 4 の buildPicUi 関数を見てください) と、オプションのみです。この場合、唯一のオプションは revert = true です。このオプションは、その写真がドロップ・ゾーンに置かれる場合以外はその写真を元の位置に戻す必要がある、ということを表しています。ドロップ・ゾーンは initDropZones によって作成されます。
initDropZones はページ上で、リストの中、リストの外、そしてゴミ箱として指定された最下部の領域、という 3 つの領域を使います。そしてそれぞれの領域に対して、あるオブジェクトがドロップ・ゾーンにドロップされるとハンドラー (onDrop) が呼び出されるように設定します。また initDropZones は、どんな種類のオブジェクトをドロップできるかに関するフィルターも設定します。各ドロップ・ゾーンは、「pic」クラスを持つオブジェクトのみを受け付けます。pic クラスというのは、リスト 4 の buildPicUi 関数の中で各写真に対して設定したクラスです。つまりドロップ・ゾーンにドロップできるのは写真のみであり、また写真を受け付けるドロップ・ゾーンの中にのみ写真をドラッグして挿入することができます。各ゾーンには異なるハンドラー関数があります。では adToTrash ハンドラーを見てみましょう (リスト 6)。
リスト 6. addToTrash JavaScript 関数
function addToTrash(pic){
pic.style.left = "";
pic.style.top = "";
$("trash").appendChild(pic);
Effect.Puff(pic.id, {duration: 0.8});
model.deletePic(pic.id);
}
|
この関数はいくつかのことをします。まず、あるオブジェクトをドラッグする際に作成される CSS の一部をクリーンアップします。次に写真を「trash」ゾーンに追加します。Prototype に用意された便利な表記方法である $("trash") の使い方に注目してください。次に、Puff という、Scriptaculous のエフェクトを使っています。Puff は写真が消えていくように見せるビジュアル・エフェクトであり、その写真が削除されたことを示します。これが Scriptaculous で言う、コンビネーション・エフェクトです。つまり Scriptaculous ライブラリーのコア・エフェクトを組み合わせて使うのです。(他にもさまざまなエフェクトを使うことができます。) 最後に、モデル (Organizer オブジェクト) に対する呼び出しを行い、このオブジェクトをデータベースから削除するための Ajax コールを行います。この Organizer オブジェクトをリスト 7 に示します。
リスト 7. Organizer クラス
var Organizer = Class.create({
initialize : function(pix){
this.pixMap = {};
this.pixList = pix;
pix.each(function(pic){
this.pixMap["pic"+pic.id] = pic;
}.bind(this));
},
updatePic : function(picId, data){
var photo = this.pixMap[picId];
params = [];
// Rails uses _method since browsers
// do not properly send HTTP PUT and DELETE
params["_method"] = "put";
// Rails uses className[propertyName] for
// request parameters to auto-bind them to
// object properties
$H(data).each(function(pair){
params["photo["+pair.key+"]"] = pair.value;
});
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
},
deletePic : function(picId){
var photo = this.pixMap[picId];
var params = {};
params["_method"] = "delete";
var options = {
method : "post",
parameters : params
};
new Ajax.Request("photos/"+photo.id,options);
}
});
|
このクラスでは興味深いことがたくさん行われています。このクラスは Scriptaculous と同様、Prototype を多用しています。例えばクラスを作成するために Prototype を使っています (Class.create(...))。initialize 関数を見てください。この関数は Organizer インスタンスを作成するときに呼び出されます。この関数は Prototype によって JavaScript の配列とオブジェクトに追加される each 関数を使っています。each 関数は多くのプログラミング言語に見られるおなじみの構成体ですが、JavaScript にはありません。今回の例では、each 関数は Prototype の bind 関数と組み合わされています。bind 関数は Prototype によって JavaScript の function object に追加される関数です。要するに bind 関数は現在のコンテキストを関数にバインドします。この場合は、each 関数によって呼び出されている匿名関数の実行中に Organizer の pixMap フィールドを変更できる必要があります。バインドがないと this.pixMap が意味を持たなくなってしまいます。また、updatePic 関数が Prototype の別の構文ショートカットである $H() という表記を使っていることにも注目してください。これによって、each 関数などをオブジェクトに追加する、Prototype の Hash オブジェクトが作成されます。これも Prototype が他の言語から借用して JavaScript に実現した、おなじみの構成体です。最後に、ここでも updatePic と deletePic は共に Prototype の Ajax.Request 関数を使ってサーバーに対するクロス・ブラウザー対応の Ajax リクエストを作成しています。
サンプル・アプリケーションを実行する
サンプル・アプリケーションを起動するためには、Rails を起動するだけです (例えば ruby script/server start など)。すると、このアプリケーションがブラウザーに表示されます (図 2)。
図 2. 写真整理ツール用のサンプル・アプリケーション
このページがロードされる際に Firebug のようなツールを使うと、データが非同期にロードされる様子を見ることができます (図 3)。
図 3. 最初のデータがロードされる様子を Firebug に表示したも
写真を 1 つのリストから別のリストにドラッグするか、または画面の一番下にあるゴミ箱にドラッグすると、別の Ajax リクエストが起動することがわかるはずです (図 4)。
図 4. ドラッグ・アンド・ドロップによって起動された Ajax リクエスト
これで、デスクトップ風の機能を大量に持つ Web アプリケーションができました。このアプリケーションは、Scriptaculous の持つ高度なコントロールとエフェクト、そして Prototype によって単純化された Ajax を使って作成されたのです。
まとめ
 |
developerWorks の Ajax リソース・センター
Ajax resource center にアクセスしてください。ここは Ajax アプリケーションを開発するための無料のツールやコード、そして情報が用意されたワンストップ・ショップです。また、Ajax のエキスパートである Jack Herrington がホストする活発な Ajax コミュニティー・フォーラムは、あなたが今まさに探している答え持っているかもしれない開発者仲間と交流する手段となります。
|
|
この記事は JavaScript ライブラリーに関する 3 回シリーズの第 2 回として、Scriptaculous JavaScript ライブラリーが提供するコントロールとエフェクトの一部について説明しました。また Scriptaculous がどのように Prototype の上に構築されているため、Ajax 開発が容易になるのかを見てきました。そして Scriptaculous のコントロールとエフェクトを Prototype の Ajax と組み合わせ、リッチなユーザー・エクスペリエンスを作成することがいかに容易かも学びました。ただしこうしたことは、ほんの全体のほんの一部にすぎません。Scriptaculous にはこれらの他にも、Web アプリケーションの中で使用できるいくつかのコントロールや、まだまだたくさんのエフェクトがあるので、それらについて時間をかけて調べる価値があります。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| Sample code | wa-aj-ajaxpro2.zip | 170KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | |  | Michael Galpin は 1990年代後半から Web アプリケーションの開発に従事してきました。彼は、California Institute of Technology で数学の学位を取得しており、現在は、カリフォルニア州サンノゼにある eBay にアーキテクトとして勤務しています。 |
記事の評価
|