Seaside チュートリアル(その4)

年末に向かって忙しくなり出すこの時期、紅葉と同期してスケジュール表もイエローサインやレッドサインが目立ち始める今日この頃。そんなこんなでいろいろあって、すっかりほったらかしだったのです。ごめんなさい。

さて、気を取り直して、Seaside チュートリアルの第4回は、いつものA Seaside tutorialさんから、Call/Answer: Displaying another componentです。レンダリングばっかの話からいよいよ一歩踏み出します。面白くなってきましたよ〜。

他のコンポーネントを表示したいっ!

WAComponentのサブクラスは「ビジュアルコンポーネント」になります。これを #registerAsApplication: すると、Seaside のフレームワークによって URL に紐づけられる(ディレクトリアクセスできる)わけですが、複数のビジュアルコンポーネントを渡り歩きたいとき――つまり入力フォームと一覧、個別詳細 を持つようなごくごく普通の Webアプリケーションを作るとき、どうしたらいいのでしょう?

まさか、別々に登録してハイパーリンク!? ――なわけないです。通常 Webアプリケーションは一つのルートコンポーネントを持っていて、そこがエントリーポイントになります。そしてユーザさんの操作の結果、他のいろいろなコンポーネントが表示されるというわけです。

ではでは、さっそくやってみましょう。以前作った HelloComponent から PersonalInfomationView コンポーネントを呼んでみます。魔法の呪文は、#call: メッセージです。

まず、HelloComponent を以下のように改造します。

renderContentOn: html
    html heading: 'Hello world' level: 1.
    html anchorWithAction: [self editPersonalInfomation] 
        text: '個人情報の編集'

editPersonalInformation
    self call: PersonalInfomationView new.

#editParsonalInformation メソッドで、ParsonalInfomationView コンポーネントインスタンスを作って、それを #call: に渡しています。これで ParsonalInfomationView が表示されるって塩梅です。

ただ、これだと行ったきりなので、 ParsonalInfomationView にも ちょっと手を入れてみます。saveボタンを押したときに、呼び出しもとに センドバックするようにしましょう。

戻るときの魔法の呪文は ラミパスラミパ・・・ じゃなくって #answer です。

save
    self answer

#call: で他のコンポーネントに表示制御を渡した後は #answer で表示の制御を呼び出し元に返します。つまり Seaside コンポーネントの Call/Answer は、モーダルダイアログの Open/Close と同じと考えることが出来ます。

行ったり戻ったり

とは言え、上記コードの Call/Answer は、ただ行って帰ってくるだけ、冷静に考えればただのハイパーリンクと一緒です。今度はもうちょっと Webアプリらしくしてみましょう。

"Call" は モーダルな処理で、#call: メソッドは、呼び出し先コンポーネントが #answerメソッドをコールするまで、決して return しません。それを念頭に置いて、HelloComponent >> #editParsonalInfomation をこんな感じにしてみます。

editPersonalInformation
    | v |
    v _ PersonalInfomationView new.
    self call: v.
    self inform: 'Hello ' , v name

つまり、#call: を実行して戻ってきたら、v には入力値が設定済みってわけなので、もう name が取ってこれると言うわけです。これで ParsonalInfomationView を表示し、answer した後、その情報を表示するようになります。

さて、ここで一つ 面白い問題を考えてみましょう。

ユーザがフォームに入力して、[save]ボタンを押します。その後ブラウザの[戻る]を押してフォームの入力値を直してからもう一度[save]を押したらどうなるのでしょうか?

最初の[save]を押した後、メソッドは inform:呼び出しの真っ最中のハズです。しかしユーザがブラウザの[戻る]ボタンを押すと、メソッドはParsonalInfomationView を #call:した状態になります。

なんと! よぉく考えればコレって、一つのメソッドの実行ステップが進んだり戻ったりしてるって事になるじゃないですか!そう、ブラウザの[戻る]ボタンを押すとサーバ側で実行中のメソッドの制御まで巻き戻っちゃうんです。

基本的に、Seaside は メソッドの実行状態のスナップショットを取っているので、[戻る]ボタンのレスポンスとして 「巻き戻し」が実行できるのです。素晴らしい!(・・・って、ちょっと煽りすぎだったかしらん(^^;)

これは Seaside が継続サーバーだから出来る強力な機能なのですが、この点については後の回で詳しく説明するつもりです。*1

呼び出し元に値を返す

#answer メソッドには引数を取るバージョン「#answer:」もあります。#answer: はこの引数で与えられたオブジェクトを呼び出し元へ返します。よくある使い方の一つがブール値を返してユーザが操作をコンプリートしたかキャンセルしたかを判断する、というものです。早速やってみましょう。

現在のPersoralInfomationView にはキャンセルボタンがないので、まずはそれを追加します。

renderContentOn: html

    html form: 
        [html text: 'お名前'.

                ・
                ・
                ・

        html submitButtonWithAction:
                 [self save] text: 'Save'.

        "↓追加↓"
        html submitButtonWithAction:
                 [self cancel] text: 'Cancel'
        ].

save メソッド と(新顔の)cansel メソッドもコレに合わせてこんなふうに。

save
    self answer: true

cancel
    self answer: false

そして、HelloComponent の #editParsonalInformation を以下のように修正すれば、キャンセルを押したときには、入力情報を表示しないようになります。

editPersonalInformation
    | v |
    v _ PersonalInformationView new.
    (self call: v) ifFalse: [^nil].
    self inform: 'Hello ' , v name

もちろん、answer: ではブール値だけではなく、文字列やその他もろもろを戻せるので、可能性は無限大です。(←煽りすぎ)

注目!ビルドイン ダイアログ!!

さて、もはや使い慣れた感のある #inform: メソッドですが、
ちょっとそのコードを覗いてみましょう。(WAComponent クラスの convenience カテゴリにあります)

inform: aString
    self call: (WAFormDialog new addMessage: aString)

#inform: は、call: を使って WAFormDialog を起動しているのが解ります。convenience カテゴリには他にも #inform: に似たメソッドが定義されています。

#confirm:

良くある[Yes][No]ボタン付きの確認ダイアログです。 Yes を選択すると true、No を選択すると false を返します。

#request:

隣に[OK]ボタンの付いたインプットボックスのある画面を表示します。ユーザが文字列を入力して[OK]をクリックすると、入力した文字列が呼び出し元に return されます。

バリエーションに、インプットボックスの初期値をセット出来る #request:default: と、ボタンのテキストを変えられる #request:label:、その両方できる #request:label:default: があります。

ありがち(?)ミスにご注意

Seaside 初心者が良くやりがちなミスに、#renderContentOn: メソッドの中で 他のコンポーネントを #call: しちゃうことがあるそうです。でも絶対にしちゃいけません。

レンダリングメソッドは、まさにレンダリングの真っ最中。そのコンポーネントのまさに今の状態を表示しようとしているわけで、他のコンポーネントを call: するなら、それは状態変更してコールバックされたにやるべきお仕事。

そもそも、まっとうな Seaside ユーザに、レンダリング中に call: しようなんて動機は全くないハズ。もし、あるコンポーネントの中に他のコンポーネントを表示したいのなら、call: するでなく、Embedding component の項を読むがよい。

――と、書いてあったのですが、ん〜〜、他のコンポーネントを中に組み込んで表示しようとして call: しちゃう、、てのが、そんなにありがちな気がしないのはなぜでしょう。もっとも、あたしを含めて Seaside 初心者なるものの存在がとてもレアケースで、その中で「典型的」といわれても あまり実感が沸かないからかもしれない(笑)

ちなみに実際にコレをやると、レンダリング真っ最中に他のコンポーネントにジャンプしちゃうので、結果どうあがいてもcall:先のコンポーネントしか表示することしか出来なくなります。(call:元は一生見れない。しくしく(TT )



さて、今回はここまで。うにゃうにゃ、まいどまいど「構成そのまんまで訳は超訳」という本シリーズですが、いいのかなぁ。もうちょっと独自色が出せればいいのですが、猫はいっぱいいっぱいで、なぞるだけで精一杯です。

ちなみに(前回からずっとなのですが) ParsonalInformationView とすべきところを ParsonalInfomationView としてしまっているのはナイショです。

今回はなんだか みょーにテンションが高く、読みにくくなってしまって申し訳ないです。

*1:と、書いてあったけど、「The "back" button」の項は未稿なのよね