ObjectSquare [2008 年 9 月号]

[技術講座]


Seaside へ GO!!

-- 楽々サーバサイド Web プログラミング --

 
seaside logo
第2回:コンポーネントの作成
ブループレイン
梅澤 真史

1. はじめに

1.1 コンポーネントで Web プログラミング

今回から Seaside を使ったプログラミングに入っていきましょう。 Seaside ではコンポーネントというものが開発の基本になります。 まずはコンポーネントとは何なのかを簡単に解説し、その後で、実際にサンプルアプリを作っていくことにします。

2. コンポーネントとは?

2.1 V+C = コンポーネント

Seaside におけるコンポーネントとは、表示、およびイベントハンドリングを行う部品のことです。

GUI のライブラリでも、まさに「コンポーネント」と呼ばれるものが登場します。 (場合によってはウィジェットやコントロールなどと言うこともあります。 この辺りの用語はフレームワークによって多少のブレがあります)。 皆さんもボタンコンポーネント、リストコンポーネントなどといった言葉を耳にしたことがあるでしょう。 いわゆる GUI を構成する個々の部品を指し、表示とイベントハンドリングを行うことになっています。 例えばボタンのコンポーネントであれば、ボタンの形を表示し、マウスクリックというイベントをハンドリングして何らかのアクションを行うのです。

 

コンポーネントは MVC(モデル・ビュー・コントローラ) のアーキテクチャパターンに照らし合わせると、「MVC の V と C をまとめたもの」と考えることができます1

 

なぜ、V と C を一つにまとめているのでしょうか。 簡単に言えば、そのほうが効率が良いからです。

 

クラシックな MVC のクラス図を見てみましょう。

図 1: クラシックな MVC
図 1: クラシックな MVC

図中においてコントローラとビューとは相互参照しています。 つまり互いの関連が強く、多くの場合において、一つにまとめたほうが合理的なのです。

 

このため、多くのモダンな MVC 実装では、V と C を一体化することとなりました。 現在広く使われているのはクラシックな MVC ではなく、VC 一体型の MVC をもとに、さらにアレンジを加えていったものです2

 

MVC には実に様々な発展系があります。 紙面の都合上、ここでは詳しく説明できないのですが、興味のある方は、Martin Fowler がまとめている GUI Architectures のページを読んでみると良いでしょう。


*1 MVC について詳しくは HappySqueaking!! の第 5 回などをご覧下さい。末尾の参考文献にも多くの情報があります。

*2 代表的なものに MVP(モデル・ビュー・プレゼンター) などがあります。

2.2 Web の世界での MVC

GUI にくらべて、Web アプリの世界では MVC の歴史が浅く、まだそれほど洗練されたものになっていないのが現状です。

 

典型的には以下のような形になります。

図 2: Web における MVC
図 2: Web における MVC

MVC はもともと順番を規定しないインタラクティブなやり取りのために作られたアーキテクチャパターンなのですが、Web での MVC は下記のように手続き的です。

  1. ユーザからのリクエストに応じてコントローラが起動される
  2. コントローラはデータベース等からモデルを取り出し操作する
  3. コントローラは表示のための適切なビューを選択する
  4. ビューは、モデルを参照し HTML ページを作る
  5. レスポンスをユーザに返す

リクエストごとに 1-5 までの処理が行われ、処理が進んでいきます。

 

これは、MVC model 2 などと呼ばれることが多い Web アプリケーションフレームワークで典型的な MVC です。 困ったことに 、GUI におけるクラシックな MVC に比べても、だいぶ劣ったものになってしまっています。

 

まず、コンポーネントとしての V と C が存在しません。 あくまでコントローラがビューに対して指示を行うという形を取っています。

MVC model 2 では、ビューはロジックを一切持たず、モデルを参照するだけという受け身の形が推奨されます。 ロジックが入るべき場所はコントローラと考え、ビューとコントローラの独立性を高めるという名目です。 しかし実際にはコントローラのロジックがビューに混入してしまうことが多々あります。

例えば参照するモデルが単一ではなく、複数のモデルの集合となっている場合、それらをイテレートしてリストやテーブルとして表示しなければならないという問題が生じます。 こうしたループの処理は、HTML を組み立てるビュー側に入りがちです。 また、集合の数に応じて一つのリストで表示するか、タブによる切り替えを前提として一部を表示するかなど、さらに複雑なロジックが入っていくこともあります。

プレゼンテーションに関わるロジックは、VC 一体としてのコンポーネントがひとまとめで持つとスマートなのですが、MVC model 2 では、2 カ所に散らばってしまいます。 V と C の関連は本来強いものなのに、それを無理に分けようとするために面倒なことになっているのです。

 

また、MVC 分割の粒度が荒いという問題もあります。 GUI の MVC は、画面を構成するウィンドウにいくつものコンポーネントが入れ子として貼り付けられ、それぞれがイベントを受けつけ、表示の更新を行うという、分割統治型になっています。 しかし Web の MVC では、コントローラはリクエスト単位で都度立ち上がり、ページ全体のビューに対して指示をするという形になってしまっています。

複雑な要素を持つモデルやビューを相手にする場合に、これではコントローラの処理が爆発してしまうことになります。

 

MVC の良いところは責務の分割と、それらのインテリジェントな連携です。 しかし典型的な Web の MVC は、「M と V と C を分ける」ことに精一杯で、相互の連携に関しては、甚だ弱いといわざるを得ません。 粒度の大きなコントローラが、指示待ちのビューやモデルを一手にコーディネートするという姿になっています。

 

本来の MVC の利点をきちんと実現できなかったのが、これまでの Web の MVC です。 このため MVC というわりには、ビューやコントローラの再利用が難しいという状況が生じているのです。

2.3 Seaside における MVC

一方、Seaside では VC 一体型の MVC を本格的に実現しています。

 

図にすると以下のようになります。 コンポーネント (WAComponent) がちゃんと存在していますね。

図 3: Seaside での MVC
図 3: Seaside での MVC

既存の Web での MVC で「コントローラ」とされていた部分は「ディスパッチャ (WADispatcher)」と名前を変えています。 ディスパッチャはリクエストの URL を基に、適切な「アプリケーション (WAApplication)」を選択するためだけに存在します。 GUI では、ちょうどスタートメニューからアプリケーション名を見て選ぶような感じです。

 

Web はマルチユーザの環境なので、「アプリケーション」のインスタンスは複数立ち上がらなければなりません。 つまり正確にはアプリケーションそのものではなく、ルートとなる「コンポーネント (WAComponent)」が、セッションごとに立ち上がります。 GUI にあてはめると、アプリケーションを選択した後に、ウィンドウが一つ開いたと考えると良いでしょう。

 

ルートコンポーネントはメモリ上で待機し、それぞれがループを行います。 イベントを受けつけ、必要に応じて自身の表示更新を行い、インタラクティブにユーザとやり取りをします。 リクエストごとに都度立ち上がるコントローラ、コントローラの指示を待つ受け身のビューというものは存在しません。

 

コンポーネントは内部にコンポーネントを入れ子として含むことが可能です。 ルートのコンポーネントがウィンドウ、入れ子になったコンポーネントが、ボタンやリストなどのウィジェットと考えることができます。

 

各コンポーネントは、状態をそれぞれ別個で保持します。 典型的な Web アプリフレームワークでありがちな、ページ間にまたがるデータを一つのセッション辞書に詰め込むようなアプローチとは一線を画しています。

 

まさに至れり尽くせり、といった感じではないでしょうか。

 

MVC を謳っている Web アプリケーションフレームワークは多くありますが、Seaside のようにコンポーネント単位で開発が行えるようになっているものは、残念なことに極めて少数派です。

 

ただし、流れは確実にこちらのほうに向かっています。 最近では、コンポーネントベースの MVC を Web に導入するフレームワークも増えてきています。 Java でいうと Click FrameworkWicket、Ruby では Cells などを挙げることができます。 これらについては末尾で簡単に紹介しておきましたので、果たして Seaside の域に達しているか、比較してみると面白いでしょう。

3. 最初のコンポーネント

コンポーネントの説明はこのくらいにして、以後は実際にコンポーネントを作っていくことにします。

 

Seaside には WAComponent というコンポーネントの抽象クラスが用意されています。 このクラスを継承することで、新たなコンポーネントをシステム内に作成することができます。

3.1 コンポーネントクラスの定義

早速、クラス定義を行いましょう。 Smalltalk では、外部エディタ等は使わず、組み込みのクラスブラウザを使ってプログラミングを行うスタイルが主流ですので、まずはそのブラウザを立ち上げることにします。

 

SeasideJOnePlus の場合、左上に Browser と書かれたアイコンがすでに表示されています。 これをクリックするとブラウザを起動できます3

図 4: クラスブラウザのアイコン
図 4: クラスブラウザのアイコン

5 つのペインからなるブラウザが立ち上がります。

図 5: Smalltalk のクラスブラウザ
図 5: Smalltalk のクラスブラウザ

Eclipse の経験がある人は、「Java 参照パースペクティブ」というものを使ったことがあるかもしれません。 レイアウトがそっくりですね4

 

それぞれのペインが何を意味するかを図にまとめてみました。

図 6: Smalltalk のクラスブラウザ
図 6: Smalltalk のクラスブラウザ

まず今回のプログラムのためのクラスカテゴリを定義しましょう。 クラスカテゴリとは Java のパッケージに該当するものです。

 

クラスカテゴリペイン上で、右クリックメニューを出し、「項目を追加...」を選びます。

図 7: クラスカテゴリの追加
図 7: クラスカテゴリの追加

名前を聞かれますので、'SeasideGo-Lesson' と入力してください。

 

これでクラスカテゴリが出来上がります。 コードペインには、クラス定義のためのテンプレートが表示されます。

Object subclass: #NameOfSubclass
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SeasideGo-Lesson'
 

ここでは、WAComponent を継承した、MyFirstComponent クラスを定義することにします。 以下のように書き換えましょう5

WAComponent subclass: #MyFirstComponent
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SeasideGo-Lesson'
 

編集後、Alt+s (Mac では Cmd) を押すと、コードがコンパイルされ、MyFirstComponent がシステム内に出来上がります6

図 8: 最初のコンポーネントの定義
図 8: 最初のコンポーネントの定義

*3 以前はアイコンをドラッグ&ドロップする必要がありました。2008/07/21 版からクリックで起動します。

*4 英語では "Java Browsing perspective" です。もともと Eclipse は Smalltalk の開発環境 (Envy/Developer) と開発元が同じ (Object Technology International) だったので、その名残がこのパースペクティブとして残っているわけです。

*5 補完機能がありますので、WAComp くらいまで打ち込んだ後、Alt+q を押すと、WAComponent としてくれます。

*6 メニュー派の人は、コードペイン上で右クリックをし、「了解」を選びます。

3.2 Hello, World!!

さて、MyFirstComponent が定義できたので、定番として 'Hello, World' の表示を行う処理を実装してみましょう。

 

表示のことを Seaside ではレンダリングと呼びます。 レンダリングのためには、renderContentOn: というメソッドをオーバーライドします。

 

メソッド定義の前に、メソッドカテゴリを作ります。 メソッドカテゴリペイン上で右クリックし、「新規カテゴリ...」を選びます。

 

続いてお勧めのメソッドカテゴリ名が表示されます。 レンダリングの処理なので、"rendering" を選べばよいでしょう。

図 9: メソッドカテゴリの作成
図 9: メソッドカテゴリの作成

コードペインがメソッド定義のためのテンプレートに変わりましたね。

 

'Hello, World' と表示させるには、以下のようにします。

MyFirstComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    html text: 'Hello, World'
 

Alt+s でメソッドをコンパイルします。 最初のメソッド定義なので開発者のイニシャルを聞かれます。適当に入れておきましょう7

図 10: メソッドの定義
図 10: メソッドの定義

次に、クラスサイドスイッチを押して、クラスメソッド側に移動してください。

図 11: クラスサイドスイッチでクラスメソッド側に移動
図 11: クラスサイドスイッチでクラスメソッド側に移動

MyFirstComponent はアプリケーションのルートとなることができるコンポーネントなので、canBeRoot というクラスメソッドを定義する必要があります。

カテゴリは 'accessing' とでもしておきましょう。

MyFirstComponent class >> canBeRoot  ('accessing'カテゴリ)
canBeRoot
    ^true
 
図 12: クラスメソッド canBeRoot の定義
図 12: クラスメソッド canBeRoot の定義

これで用意ができました。 開発環境の解説をしながらやっているので、長く感じますが、要するにメソッドを 2 つ書いただけですね。

 

では Web ブラウザからアクセスしてみることにしましょう。

 

まだ MyFirstComponent はアプリケーションとして登録されていないので、まずはアプリケーション管理ツールを開くことにします。

http://localhost:9090/seaside/config
 

ユーザ名とパスワードはそれぞれ admin/seaside となっています。

図 13: アプリケーション管理ツールの起動
図 13: アプリケーション管理ツールの起動

管理ツールの上部に現在起動できるアプリケーション群がリストされています。 今回は、MyFirstComponent をルートコンポーネントとした新たなアプリケーションを登録します。 名前はシンプルに first としましょう。

Add entry point というフォームがあるので、そこに記入を行い、Add ボタンを押します。

図 14: アプリケーションのエントリポイントの指定
図 14: アプリケーションのエントリポイントの指定

first アプリケーションの設定をするための別ページに切り替わります。 ここで、先ほど作成した MyFirstComponent をルートとして指定することにします。 Root Component の欄を選び、プルダウンメニューから選択して、Save を押します。

図 15: アプリケーションのルートコンポーネントの指定
図 15: アプリケーションのルートコンポーネントの指定

first というアプリケーションがこれで登録されました。 Close でもとの画面に戻ると、first が一覧表示されていることを確認できます。

図 16: 登録された first アプリケーション
図 16: 登録された first アプリケーション

以後は以下の URL でアクセスできるようになります。

http://localhost:9090/seaside/first
 

無事に表示が行われました。

図 17: Hello, Worldの表示
図 17: Hello, Worldの表示

さて、今までの作業を失わないために、ここで一旦環境をセーブしておきましょう。

 

Squeak のデスクトップの空き領域で右クリックするとメニューがでてきます。 「保存」を選んでセーブを行います。 こうしておくと、次回立ち上げ時には、サーバの設定やウィンドウの配置も含め、まったく同じ状態から Squeak が再開します。

図 18: ワールドメニューからの保存
図 18: ワールドメニューからの保存

*7 第一回目の Web 版コードブラウザと全く同じ動きです。

4. レンダリング

それではコンポーネントの表示部分のコードを改めて見てみましょう。

MyFirstComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    html text: 'Hello, World'
 

html という引数でやってくるレンダラ (WARenderCanvas) に対して、text: というメッセージを送っています。 Seaside では HTML の作成はすべてこのメッセージ送信によって行われるのです。

 

WARenderingCanvas には、HTML 作成のための様々なメッセージを送ることができます。 今日の日付が表示されるように少し変えてみましょう。

MyFirstComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    html heading: 'Hello, World'.
    html heading: Date today level: 2
 

Alt+s で保存し、Webブラウザを再読み込みすると、以下のように表示されます。

図 19: レンダリングの書き換え
図 19: レンダリングの書き換え

'Hello, World' という文字列が <h1> で、今日の日付 (Date today) が <h2> として出るようになりました。

4.1 メッセージ送信による HTML 生成

一般的な Web アプリケーションフレームワークでは、テンプレートエンジンを使って HTML を作成します。

つまり、HTML のタグが埋め込まれたテキスト(テンプレート)に、プログラムコードからの値が動的に差し込まれるという形を取るのです。

 

Seaside であえて似たようなことをすると以下のようになるでしょう。 先ほどの例と同じ処理をテンプレートエンジン風に書いてみました。

MyFirstComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    | template contents |
    template := '<h1>{1}</h1> <h2>{2}</h2>'.
    contents := template format: {'Hello, World'. Date today}.
    html html: contents
 

Squeak が持つ文字列展開のメソッド format: を利用しています。

まずテンプレートとなる文字列 (template) を用意します。 {1} や {2} という部分は、後から実際の値が入る箇所になります。

 

次に format: によって値が展開された文字列 (contents) を作ります。 引数として配列を指定しており、1 番目の要素を 'Hello, World'、2 番目の要素を今日の日付オブジェクト (Date today) にしています。

 

最後に contents を html: によってレンダラ (WARenderingCanvas) に渡しています。

 

面倒ですね。 この例では一つのメソッドに書きましたが、ビューとコントローラを分けるような Web アプリケーションフレームワークにおいては、template の文字列がビュー、format: で展開する部分がコントローラとして、それぞれ別ファイルに分かれることになります。 ある処理を把握するためにいろいろな箇所を見なければなりません。 また、ビュー側は単なる文字列となってしまうので、タグを閉じ忘れないようにするなど、新たな注意が必要になります。 一方でレンダラを使うアプローチにでは、開発者が意識しなくとも、メッセージ送信によって、常に正しい HTML が生成されていきます。

 

実は Seaside もごく初期のバージョンにおいてはテンプレートエンジンを持っていました8。 また、「Seaside でテンプレートエンジンを作った!」という人も時折現れます。 しかしそうしたものは使われずに廃れていくのが常となっています。

 

もともとテンプレートエンジンを使うというアイデアは、Web アプリにおける見栄え(ビュー)部分と、ロジカルな処理(コントローラおよびモデル)とを分けたいという要求から生じました。 1990 年代の後半から 2000 年にさしかかるころまでは、非常に合理的な考えであり、Web デザイナと、プログラマの作業の分担をこれにより可能にしたのです。

 

しかし Seaside は、CSS が普及した 2002 年以降に作られました。 そのころになると、見栄えの調節は CSS で行うほうが主流となり、HTML はどちらかというと文書のロジカルな構成を表すものとして考えられるようになっていきました。

 

Seaside は CSS に関しては完全に分離を行います9。 しかし、HTML に関してはコンポーネント側のコードでプログラム的に作るというアプローチを取ります。

 

見栄えに関しては CSS、プレゼンテーションロジックに関してはコンポーネント、ビジネスロジックに関してはドメインモデルが受け持つという責務の分担になっているのです。

 

図にすると以下のようになります。

図 20: Seaside の責務分担
図 20: Seaside の責務分担

従来のコントローラが中心となるアプローチでは、プレゼンテーションロジックの位置が定まらない傾向があります。 ビュー側とコントローラ側のどちらにも持たせることができるので、明確な指針が必要です。 またコントローラの責務も、プレゼンテーションロジックとビジネスロジックとが混在してしまい、わかりにくくなりがちです。

図 21: 従来の Web フレームワークの典型的な責務分担
図 21: 従来の Web フレームワークの典型的な責務分担

Seaside は、思い切ってテンプレートエンジンを捨てました。 それによりデザイナとプログラマの責務分担を改めて明確にしたのです。


*8 Seaside 0.94 です。末尾に資料を載せておきましたので興味のある方はご覧ください。

*9 CSS については次回以降に扱います。

4.2 レンダリングの例

Seaside のスタイルに慣れるべく、もうすこし高度なレンダリングを試してみましょう。

 

リストとテーブルを表示してみることにします。

MyFirstComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    | array |
    array := {'Hello, World'. Date today. Time now }.
    html orderedList: [array do: [:each | html listItem: each]].
    html table: [ 
        html tableRow: [ 
            array do: [:each | html tableData: each]
        ]
    ].
 

まず表示用の配列 (array) を作成しています。 文字列 'Hello, World' と、今日の日付 (Date today)、現在の時刻 (Time now) オブジェクトが入っています。

 

次にその配列を表示させます。 順序付きリストとして出したいのでレンダラに orderedList: を送っています。 ブロック内で array を do: によってイテレートし、各要素 (each) を listItem: により表示しています。

 

テーブルの場合もほぼ同じです。 table: でテーブルの大枠を作り、行を tableRow: で作ります。 その中で array をイテレートして、tableData: で各要素を表示させています。

 

メソッドを書き換えて Web ブラウザを再読み込みすると以下のようになります。

図 22: 複雑なレンダリングの例
図 22: 複雑なレンダリングの例

メッセージ送信による HTML 作成では、タグの閉じ忘れも、タグの入れ子関係がずれたりすることもありません。 レンダラが自動的に正しい HTML を作ってくれます。 たとえレンダラへのメッセージ送信がおかしな書き方になっていたとしても、メソッドを Alt+s でコンパイルしたときに、Smalltalk の文法としてチェックが行われます。 また Smalltalk の強力なデバッガを使い、間違っている箇所を実行時に突き止めることも簡単にできます。

 

いわばレンダラは HTML 作成のための DSL を提供してくれるのです。 上記の renderContentOn: は、何が行われるのかすぐに把握でき、英語のように、流れるように読めるコードになっています。

HTML タグと動的箇所指定用の特殊なタグ、テンプレート言語 (ビュー側)、通常のプログラムコード(コントローラ側)とが入り交じる従来の手法よりも、わかりやすく感じないでしょうか。

 

さらにレンダリングをいろいろと試したい人は、WARenderCanvas のクラス定義を眺めてみると良いでしょう10


*10 クラスカテゴリペインでメニューを出し、「クラスの検索...」でブラウズできます。

5. コールバック

では、続いてコントローラのコードをコンポーネント上に書いていくことにします。 多くの GUI フレームワークと同様、Seaside ではイベントに対するコールバック(イベントハンドラ)を書くことで、コントローラの処理を実現できます。

Web アプリケーションの場合、ユーザからのイベントは、主にリンクをクリックしたときや、フォームのサブミットボタンをクリックしたときに起こります。 こうしたイベントに対し、Smalltalk のブロックを使い、コールバックを指定するのです。

5.1 リンクの利用

まずはリンクにおけるコールバックの例を示します。

MyFirstComponent>>renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    html anchor 
        callback: [self inform: Time now print24];
        with: '今の時間は?'

レンダラに anchor と送って、リンク記述用のオブジェクトを得ています11。 これに対して、callback: によりイベントハンドラ (Smalltalk のブロック) を指定します。 ブロック内部には、クリックしたときに行われる処理を好きに書くことができます。 上記の例の場合、現在の時刻を 24 時間表記の文字列で得て (Time now print24)、それを inform: に渡しています。

その後 with: を使って、リンクとして表示させる文字列を指定しています。

 

Alt+s でメソッドをコンパイルした後、ブラウザを再読込すると、以下のようにリンクが表示されます。

図 23: コールバックが指定されたリンクの表示
図 23: コールバックが指定されたリンクの表示

クリックを行うとブロックの中身がサーバ側で実行されます。 inform: の作用により、別ページに切り替わって現在の時刻が表示されます。

図 24: コールバックの実行結果
図 24: コールバックの実行結果

*11 こうしたタグ生成用のオブジェクトは TagBrush と呼ばれています。

5.2 フォームの利用

今度はフォームを使ってみます。 もう少しアプリケーションらしく、ブログなどでよく見られるコメント記入のコンポーネントを作ってみたいと思います。

 

新たにクラスを定義します。 インスタンス変数を宣言しているところがポイントです。 currentComment は、ユーザが現在入力しているコメントを保持するため、comments は、入力されたコメントをコレクションで蓄積するために使います。

WAComponent subclass: #MyCommentsComponent
    instanceVariableNames: 'currentComment comments'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'SeasideGo-Lesson'
 

変数の初期化のために initialize メソッドを定義します。 このメソッドはコンポーネントインスタンスができたときに、自動的に呼ばれます。 WAComponent の initialize をオーバーライドすることになるので、super initialize を忘れないようにしなければなりません。

MyCommentsComponent >> initialize ('initialization'カテゴリ)
initialize
    super initialize.
    currentComment := nil.
    comments := {'コメントその1'} asOrderedCollection
 

では、いよいよフォームを使ってみましょう。

MyCommentsComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    "コメントの表示"
    html orderedList: [comments do: [:each | html listItem: each]].

    "フォームの表示"
    html form: [
        html textInput value: currentComment.
        html submitButton
    ]
 

ここでのポイントは form: の利用です。 これで Web のフォームを作ることができます。 この例では内部にテキスト入力フィールドとサブミットボタンを配置しています。 テキスト入力フィールドでは、value: で現在の currentComment の値を表示させています。

 

では、アプリケーションの登録をして Web ブラウザから確認しましょう。 名前は comments とします。 MyFirstComponent と同じように、管理ツールを使って登録しても良いのですが、ここでは別のやり方として、Squeak の側からやってみます。

 

まず、ワークスペースというツールを開きます。上部のボタンから起動できます。

図 25: ワークスペースの起動
図 25: ワークスペースの起動

ワークスペースとは、任意の Smalltalk の式を書いて実行できる便利なエディタのことです。 ちょっとしたコードを実行するときに使用します。

 

以下のように書きます。

MyCommentsComponent registerAsApplication: 'comments'
 

行を選択して、Alt+d、もしくは右クリックメニューで「式を評価」を選びます。

図 26: ワークスペースからのアプリケーションの登録
図 26: ワークスペースからのアプリケーションの登録

これで MyCommentsComponent が comments というアプリケーション名で登録されたことになります。 このやり方の場合、クラスメソッド canBeRoot を定義する必要もありません。

 

それでは、アクセスしてみましょう。

http://localhost:9090/seaside/comments
図 27: フォームの表示
図 27: フォームの表示

上部にコメントがリストとなって表示され、下部はフォームになっています。 しかしボタンを押しても何も起こりません。 まだコールバックを付けていないので、これは当然のことです。

 

コールバックは、テキスト入力フィールドとサブミットボタンの両方に定義します。 以下のようになります。

MyCommentsComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html
    "コメントの表示"
    html orderedList: [comments do: [:each | html listItem: each]].

    "フォームの表示"
    html form: [
        html textInput value: currentComment;
            callback: [:newValue | currentComment := newValue].
        html submitButton callback: [comments add: currentComment]
    ]

テキスト入力フィールドのコールバックでは、フォームから入力された値 (newValue) を、currentComment に代入するという処理を書いています (currentComment := newValue)。

サブミットボタンのコールバックでは、コメントが入ったコレクション (comments) に、currentComment を追加しています (comments add: currentComment)。

 

これで完成です。 再び実行してみましょう。

図 28: フォームからのコメントの追加
図 28: フォームからのコメントの追加

無事に追加されるようになりました。 メソッド 2 つでこの程度のものは作れるということです。

6. 状態の保持

最後に Seaside らしく、継続を使った状態の保持を行ってみましょう12。 デフォルトでは、コンポーネントは最新の値を保持するのみで、ユーザのアクションごとに保持してくれるようにはなっていません。

継続を使った状態管理は強力ですが、反面メモリを消費するという欠点もあります13。 このため Seaside では、どの変数について継続を使った管理を行うのかを、指定できるようになっています。


*12 「何の事やら?」という人は第一回をご覧下さい。

*13 オンメモリのデータを DB に保存する方法などは後の回で扱います。

6.1 Back で戻ると?

先ほど作ったばかりのコメント記入アプリケーションで、デフォルトの状態管理がどのようになっているのかを確認してみましょう。

 

Web ブラウザの Back ボタンで 2 回ほど戻ります。

図 29: Back ボタンで戻る
図 29: Back ボタンで戻る

戻った後に、コメントを訂正し、再びサブミットするとどうなるでしょうか。

図 30: 戻った後にサブミット
図 30: 戻った後にサブミット

何ともおかしな事になります14。 Web で普通に見られる「Back ボタンで戻らないでください」というタイプのアプリケーションですね。

図 31: 4 番目のコメント追加になってしまう
図 31: 4 番目のコメント追加になってしまう

Back ボタンに対応させるには、変数 comments の状態を、継続によりきちんと管理する必要があるということがわかります。


*14 Webブラウザのキャッシュの実装によって動きは多少異なります。

6.2 states メソッド

継続による状態管理を指定するには、コンポーネントに states というメソッドを定義します。 このメソッドでは、継続による管理を行いたい変数群を配列の形で返します。 今回の場合は comments のみを書けば OK です (currentComment については不要です)。

MyCommentsComponent >> states ('accessing'カテゴリ)
states
    ^{comments}

Alt+s で states メソッドを定義した後、コメント記入アプリにアクセスし直し、先ほどと同じ操作を行ってみましょう。

 

今度は Back ボタン対応になっています。 存分に行き来して試してみてください。

図 32: states メソッドによる Back ボタンへの対応
図 32: states メソッドによる Back ボタンへの対応

7. まとめ

今回は Seaside のコンポーネントベースでの Web プログラミングについて学びました。 V と C を一体化させたことで、レンダリングとコールバックを使って効率よくアプリケーションを書いていけるメリットが伝わったでしょうか。

次回は複数のコンポーネントの連携に入ります。 単体でコンポーネントを使っているだけでは、まだまだ Seaside のうまみは見えてきません。 コンポーネントの継承、入れ子、呼び出しを駆使し、最小の手間でエレガントに、より本格的なアプリケーションを作っていくことにします。 お楽しみに。

8. 参考文献

Let's "do it"!!

記事の内容を5点満点で評価してください。
1点 2点 3点 4点 5点
記事に関するコメントがあれば併せてご記入ください。

© 2008 Masashi Umezawa
Prev. Index Next
Prev. Index Next