![]() |
[2009 年 6 月号] |
[技術講座]
1 年近く続いた、この連載もいよいよ最終回となりました。 仕上げはユーザエクスペリエンスの向上です。 CSS を使い、コンポーネントにスタイルを適用する方法を見ていきます。 また、Ajax による動きのあるコンポーネントの作成も扱います。
最後に新バージョンである Seaside 2.9 や、Seaside によるフルスタックの Web アプリケーション開発環境 GLASS など、今後の動向についても解説していきます。
いつものように、All-in-one パッケージである SeasideJOnePlusDB を教材用に使います。
まだ SeasideJOnePlusDB を入手していない場合は、最新版を下記からダウンロードしてください。
SeasideJOnePlusDB
今回は外部の DB を使わないので、セッションアダプタクラスはデフォルトのオンメモリ DB に切り替えておきます。 ワークスペースで以下を実行しましょう。
ToDoSessionMemory activate
これで準備は完了です。
SeasideJOnePlusDB に同梱されている ToDo アプリを使い、コンポーネントにスタイルを適用していきましょう。
第2回で、Seaside はコンポーネントと見栄えの分離を CSS で行うという話をしました。 Seaside では、コンポーネントとスタイルは、弱い関係で結びつき、独立して管理できるようになっています(図を再掲します) 。
コンポーネントと CSS を結びつけるやり方はいくつかありますが、まずはもっとも基本的な、style メソッドを使う方式を紹介します。
style メソッドの使い方は非常に簡単です。 コンポーネントに style メソッドを定義して、CSS の文字列を返すように書くだけです。
ToDoListComponent でやってみましょう。
ToDoListComponent >> style ('rendering'カテゴリ)
style ^' body { background-color: beige; } '
これで HTML の body に該当する部分の背景色が変わります。
Web ブラウザで確認してみます。
https://localhost:9090/seaside/toDoList
ユーザ名 'user1'、パスワード 'pwd1' でログインしてみてください。
背景色がベージュになっていますね。
style メソッドは、Web ブラウザからも編集可能です。
第1回で紹介したスタイルシートエディタは、実はコンポーネントの style メソッドの書き換えを行うためのものだったのです。
ブラウザ下部の "Toggle Halos" でハローを出し、スタイルシートエディタを起動してみましょう。
background-color: skyblue; などとして保存し、エディタを閉じると、コンポーネントの見栄えが即座に変わります。
ソースコードを見てみると、以下のように書き換わっています。
ToDoListComponent >> style ('rendering'カテゴリ)
style ^ 'body { background-color: skyblue; }'
インデントが少し変ですが、ちゃんとしたコードになっていますね。 このように Web ブラウザから実際の表示を見ながらスタイルを調整できるというのが、style メソッドを使う利点の一つです(もっとも skyblue の背景はいくぶん派手すぎるので beige に戻しておきましょう)。
続いて ToDoItemComponent にも style メソッドを定義することにします。
オリジナルのレンダリングは以下のようになっていました。
ToDoItemComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html html span style: 'margin-right: 10px';with: [html text: self item contents]. html span style: 'margin-left: 10px'; with: [ html anchor callback: [self edit]; with: '編集'. ]
style: を使って、スタイル指定をハードコードで行っています。 見栄えとプレゼンテーションの分離という観点では、あまり望ましいコードではありません。
style メソッドを使うと以下のようにできます。
ToDoItemComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html html span class: 'itemContents'; with: [html text: self item contents]. html span class: 'editLink'; with: [ html anchor callback: [self edit]; with: '編集'. ]
ToDoItemComponent >> style ('rendering'カテゴリ)
style ^' .itemContents { color: chocolate; margin-right: 10px; } .editLink { margin-left: 10px } '
レンダリング側では、class: でクラス名の指定のみを行っています。 対応するスタイル定義は style メソッドの側で独立して書かれるというわけですね。 こうすると、レンダリングのコードとは別に、デザイナが CSS のみを編集できるようになります。
Web ブラウザで確認してみましょう。
https://localhost:9090/seaside/toDoList
itemContents のクラス指定で、color を chocolate にしているため、ToDo 項目を追加したときの文字色がチョコレート色になっています。
もう少しレンダリングを工夫し、ToDo 項目の状態に応じて、別のスタイルが適用されるようにしてみましょう。
ToDoItemComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html | rowClass | rowClass := self item contents isEmpty ifTrue:['empty'] ifFalse: ['normal']. html div class: rowClass; with: [ html span class: 'itemContents'; with: [html text: self item contents]. html span class: 'editLink'; with: [ html anchor callback: [self edit]; with: '編集'. ] ]
項目を表示する全体を div で囲い、rowClass という変数でスタイルを指定するようにしました。 rowClass の値は、ToDo 項目の内容が空の場合は 'empty'、そうでない場合は 'normal' となります。
style メソッドも合わせて修正します。
ToDoItemComponent >> style ('rendering'カテゴリ)
style
^'
.empty {
background-color: pink;
font-size: smaller;
}
.normal {
background-color: white;
}
.itemContents {
color: chocolate;
margin-right: 10px;
}
.editLink {
margin-left: 10px
}
'
Web ブラウザに戻って、新たな項目を追加してみましょう。
https://localhost:9090/seaside/toDoList
新たな項目は内容が空なので、'empty' スタイルが適用され、フォントが小さめとなり、背景もピンク色となります。 既存の項目は 'normal' スタイルが適用されるため、通常のフォントサイズで背景が白色となります。
巻末にソースコードをまとめてあります(SeasideGo-Lesson-Chap6-CSS1.st)。 打ち込みが面倒な方はファイルインしてください1 。
style メソッドを使う方法は手軽で便利ですが、CSS の中身が増えてくると、1 メソッド内にすべてを記述するために管理が大変になってきます。 Smalltalk のコードブラウザは CSS を表示するためのものではないので、シンタックスハイライトなどもしてくれません。
style メソッド内に直接 CSS の文字列を書くのではなく、外部ファイルに保存しておき、style メソッドではそのファイルを読むだけにすると何かと便利です。
改良は簡単です。 ToDoItemComponent >> style を以下のように書き換えてみましょう。
ToDoItemComponent >> style ('rendering'カテゴリ)
style
^'./styles/item.css' fileContents
CSS のファイルが置かれる相対パスを文字列で指定し、fileContents で、その内容を取り出して返すというコードになっています。
このコードは FileMan というファイル操作ライブラリを使う書き方になっています。 SeasideJOnePlusDB には最初から入っているので、特に気にする必要はありません2。 パスの区切りが'/'で書いてありますが、FileMan が自動的に変換を行うため、上記のコードのまま Windows, Mac, Linux で動作します。
FileMan の相対パス指定では、Squeak のイメージファイルの置かれているディレクトリがカレントとなります。 SeasideJOnePlusDB の場合は、'image' の下に 'styles' というディレクトリを作り、そこに 'item.css' という CSS ファイルを置くことになります。
'item.css' の中身は、元の ToDoItemComponent >> style と同じでよいのですが、区別をつけるため色を変化させることにします。
.empty { background-color: lightcyan; font-size: smaller; } .normal { background-color: white; } .itemContents { color: darkblue; margin-right: 10px; } .editLink { margin-left: 10px }
Web ブラウザから確認しましょう。
https://localhost:9090/seaside/toDoList
'item.css' のスタイルが反映されました。 この方式ではソースコードと全く別に CSS ファイルを編集できるので、さらに開発者とデザイナが分業しやすくなります。
次に、外部サーバに置いてある CSS の参照の仕方を見ていきます。
例えば https://blueplane.jp/styles/item.css に置いてあるスタイルシートを ToDoItemComponent から参照したいとします。
HTML でいうと、<head> 内に <link> タグでスタイルシートへの URL を指定するようなイメージです。
Seaside は、HTML ページへの情報の付加のために updateRoot: というフックメソッドを提供しています。 これを下記のようにオーバーライドすればよいでしょう。
ToDoItemComponent >> updateRoot: ('updating'カテゴリ)
updateRoot: aHtmlRoot super updateRoot: aHtmlRoot. aHtmlRoot stylesheet url: 'https://blueplane.jp/styles/item.css'
引数として WAHtmlRoot という、HTML のページ全体を表すオブジェクトがやってきます。 これに対して stylesheet メッセージを送り、さらに url: で参照先の URL を指定すれば OK です。
これで <head> 内に以下の HTML が生成されることになります。
<link href="https://blueplane.jp/styles/item.css" type="text/css" rel="stylesheet">
ToDoItemComponent >> style は不要となるので、削除しましょう。
ブラウザからメニューを出し「メソッドの削除」を選んでもよいのですが、ワークスペースで以下を実行することによっても削除できます。
ToDoItemComponent removeSelector: #style
Web ブラウザから確認してみます。
https://localhost:9090/seaside/toDoList
https://blueplane.jp/styles/item.css には、色をグリーン系に変えたスタイルシートが置いてあるため、上のような結果となりました。
外部サーバが利用できる環境であれば、この方法もなかなか便利です。 ただし上記のサンプルのままではスタイルシートへの URL がハードコードされてしまうので、若干の注意が必要です。 後述の Configuration のカスタマイズを行うと、URL 指定を "Configure" リンクを使い、ブラウザの管理画面などから変更できるようになります。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-CSS2.st
*2 自分で FileMan を入れるには https://squeaksource.blueplane.jp/FileMan.html から MCZ ファイルを取得し、Squeak の画面にドロップしてロードを行います。
スタイル指定の最後は FileLibrary という仕組みを使う方法です。 これにより、スタイルに関するメソッド群を、ライブラリという別クラスに持たせ、一元管理できるようになります。 複数のコンポーネントから共通に使われるスタイルなどをまとめておくと便利でしょう。
ToDo アプリ用のスタイルをまとめるため、ToDoStyles というクラスを導入します。 スーパークラスは WAFileLibrary です。
WAFileLibrary subclass: #ToDoStyles instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ToDo-Component'
ライブラリクラスでは、CSS の文字列を返すメソッドを複数定義できます。 ただし名前はかならず 'Css' で終わるようにします。
ToDoStyles >> listCss ('css'カテゴリ)
listCss ^' body { background-color: beige; } '
ToDoStyles >> itemCss ('css'カテゴリ)
itemCss ^' .empty { background-color: yellow; font-size: smaller; } .normal { background-color: white; } .itemContents { color: darkgreen; margin-right: 10px; } .editLink { margin-left: 10px } '
さらに、スタイルを定義したメソッド名一覧を返す selectorsToInclude というメソッドも定義します。
ToDoStyles >> selectorsToInclude ('accessing'カテゴリ)
selectorsToInclude
^self class organization listAtCategoryNamed: 'css'
ここは素直に ^#(listCss itemCss) としてもよいのですが、メソッドが増える度に selectorsToInclude を書き換えるのも面倒なので、リフレクションを使い、'css' メソッドカテゴリのメソッド名リストを返す (self class organization listAtCategoryNamed:) という書き方にしています。
ToDoStyles ライブラリを使うため、ToDoItemComponent や ToDoListComponent に定義していたスタイル用のメソッドは不要になります。 下記をワークスペースで実行して、まとめて消してしまいましょう。
ToDoItemComponent removeSelector: #updateRoot:. ToDoListComponent removeSelector: #style
次に ToDo アプリが ToDoStyles を使えるように、ライブラリの追加作業を行います。 Web ブラウザ下部の "Configure" リンクで設定ツールを開いてください。
"Add Library:" という項目があるので、プルダウンリストから ToDoStyles を選択します。
"Add" ボタンを押すと、ライブラリのリストに ToDoStyles が追加されます。
"Save" で保存を行い、設定画面を閉じると、スタイルが適用された ToDo アプリになっています。
style や updateRoot: では、コンポーネントごとにスタイルを指定していましたが、ライブラリの場合はアプリケーション全体の指定となるのがポイントです(上の図ではログイン画面の背景もベージュになっています)。
スタイル適用の優先順位をまとめてみます。
FileLibrary によるスタイル指定はもっとも優先度が低いため、コンポーネント側でメソッドを定義してスタイルを置き換えることができます。 共通のスタイルを FileLibrary に入れておき、必要に応じてコンポーネント側で上書きするとスマートでしょう。
ちなみにライブラリの追加は、ワークスペースで以下のようにしても行うことができます。
"ToDoアプリを取得" app := WADispatcher default entryPointAt: 'toDoList'. "ToDoStylesを追加" app addLibrary: ToDoStyles
WADispatcher default entryPointAt: で、アプリケーション名を指定し、既存のアプリケーションを取得しています。 それに対し addLibrary: でライブラリの追加を行います。
今まではアプリケーションの取得で下記のようにしてきました。
app := ToDoTask registerAsApplication: 'toDoList'.
この書き方ではアプリケーションが新規に登録されるので、既存の設定が消えてしまいます。 ToDo アプリではセッションクラスの指定も行っており3、その設定を残したままライブラリを追加したいので、WADispatcher 経由で取得を行っているわけです。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-CSS3.st
*3 セッションアダプタクラスの指定に使っています。詳しくは第4回や第5回をご覧下さい。
Seaside は HTML をレンダリングするための優れた機構を持っていますが、最近の Web アプリは、JavaScript を積極的に使って構築することも多くなってきています。 ここでは Seaside からの Ajax の利用の仕方を見ていきます。
Seaside はデフォルトで script.aculo.us という JavaScript のライブラリをサポートしています4。
script.aculo.us を有効にするのは簡単です。 単に SULibrary という FileLibrary をアプリケーションに追加すればよいのです。
ワークスペースで以下を実行します。
"ToDoアプリを取得" app := WADispatcher default entryPointAt: 'toDoList'. "SULibraryを追加" app addLibrary: SULibrary
これで、ToDo アプリから script.aculous.us を使えるようになりました5。
Seaside からは JavaScript のコードを意識する必要はありません。 普段のように、レンダラにメッセージを送るというやり方で、適切な JavaScript を生成できます。
*4 このほか、jQuery や MooTools を Seaside から使うためのパッケージもあります。
*5 Web ブラウザから "Configure" リンクをたどって追加してもかまいません。
もっとも単純な例として、視覚効果(エフェクト)を試してみましょう。
ToDoListComponent のレンダリングのコードは以下のようになっています。
ToDoListComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html html heading: 'ToDoList: ', self session currentUser name. html form: [ self renderItemsOn: html. html horizontalRule. html submitButton on: #add of: self; with: '追加'. html submitButton on: #remove of: self; with: '削除' ]
追加ボタンが押されたときに、リスト表示部分を光らせるように修正してみます。
ToDoListComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html html heading: 'ToDoList: ', self session currentUser name. html form: [ html div id: 'items'; with: [self renderItemsOn: html]. html horizontalRule. html submitButton onClick: (html effect id: 'items'; highlight); on: #add of: self; with: '追加'. html submitButton on: #remove of: self; with: '削除' ]
まずリスト表示を行う部分を div タグで囲むようにしました。 そしてそのタグに id: により、'items' という id をつけています。
次に追加ボタンに対して、新たに onClick: で JavaScript 用のイベントハンドラを設定しています。 ハンドラ内では effect を生成し、'items' という id のタグに対して、ハイライトを行う指定をしています(html effect id: 'items; highlight')。
JavaScript は一切書いていませんが、これだけでレンダラが適切な script.aculous.us のコードを生成してくれます。
Web ブラウザから試してみましょう。
https://localhost:9090/seaside/toDoList
追加ボタンをクリックするとリスト部分がハイライトされるようになりました。
script.aculous.us は、このほかにも豊富な視覚効果を提供しています。 例えば、ソース中の highlight の部分を shake にすると、追加時にリストが震えるようになります6。 SUEffect クラスをブラウズして、実装されているさまざまな効果を試してみるとよいでしょう。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-Ajax1.st
*6 CSS がキャッシュされるため、新たな効果を有効にするには、ブラウザのリロードが必要になります。
今度は、削除ボタンが押されたときに、本当に削除するかどうか、確認するようにしてみます。 その際に、既存のページ上の背景を暗くして lightbox による強調表示を行うことにします。
lightbox を使うのはやはり簡単です。 call: の代わりに、lightbox: を使うだけです。
ToDoListComponent >> remove の中身を見てみます。
ToDoListComponent >> remove ('actions'カテゴリ)
remove self selections do: [:idx | self session removeToDoItem: (self itemComponents at: idx) item]. self initItemList
有無をいわさず削除していますね。 lightbox: で確認画面を開くように変えていきましょう。
確認用に ToDoConfirmDialog というクラスを新たに導入することにします。 属性として messageText を持っているだけのものです。
WAComponent subclass: #ToDoConfirmDialog
instanceVariableNames: 'messageText'
classVariableNames: ''
poolDictionaries: ''
category: 'ToDo-Component'
「アクセッサの作成」メニューで messageText のアクセッサも作っておきます。
レンダリングでは、messageText を表示し、'Yes' ボタンで true が、'No' ボタンで false が返るようにします。
ToDoConfirmDialog >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html html text: self messageText. html horizontalRule. html form: [ html submitButton callback:[self answer: true]; with: 'Yes'. html submitButton callback:[self answer: false]; with: 'No'. ]
ToDoListComponent >> removeに戻り、lightbox: を使う部分を追加します。
ToDoListComponent >> remove ('actions'カテゴリ)
remove | confirm | confirm := self lightbox: (ToDoConfirmDialog new messageText: '本当に削除しますか?'). confirm ifFalse: [^self]. self selections do: [:idx | self session removeToDoItem: (self itemComponents at: idx) item]. self initItemList
このコードで動作はしますが、ToDoConfirmDialog にスタイルがないとさびしいので、スタイル適用のためのメソッドも追加しておきましょう。
ToDoStyles >> lightboxCss ('css'カテゴリ)
lightboxCss
^'
#lightbox {
background: beige;
padding: 1em;
}
'
上記の CSS では #lightbox という id を指定してスタイルを定義しています。 こうすることで lightbox の見栄えを柔軟に変更できます。
削除時の動作を試してみましょう。
https://localhost:9090/seaside/toDoList
以下の図のように lightbox が開くようになります。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-Ajax2.st
Ajax 利用の最後は、ToDo リストの順番をドラッグ&ドロップで変えられるようにするというものです。 変更のあった部分のみを非同期でサーバへ反映させるという点で、本来的な意味での Ajax の例になっています。
とはいえ Seaside 側としては特に気負う必要もありません。 通常のコールバックと、ほぼ同じ感覚で書いていくことができます。
ToDo 項目のリスト表示部分を見てみましょう。 現状では、HTML の table を使っています。
ToDoListComponent >> renderItemsOn: ('rendering'カテゴリ)
renderItemsOn: html html table: [ self itemComponents withIndexDo: [:each :idx | html tableRow: [ html tableData: [ html checkbox value: false; callback: [:selected | selected ifTrue: [self selections add: idx]]]. html tableData: [html render: each] ] ] ]
script.aculous.us でドラッグ可能にできるのはリストなので、まずは以下のようにリストを使ったものに書き換えます。
ToDoListComponent >> renderItemsOn: ('rendering'カテゴリ)
renderItemsOn: html html unorderedList with: [self itemComponents withIndexDo: [:each :idx | html listItem with: [html checkbox value: false; callback: [:selected | selected ifTrue: [self selections add: idx]]. html render: each]]]
しかしこれだけでは、行頭文字が表示されてしまったり、編集時に改行が行われてしまったりなど、見栄え上の不都合が生じます。
これは CSS で調整すればよいでしょう。 先ほど ToDo 項目全体の表示に 'items' という id をつけたので、それを利用して、以下のように書くことができます。
ToDoStyles >> itemsAjaxCss ('css'カテゴリ)
itemsAjaxCss ^' #items li {list-style-type: none;} #items h3 {display:inline;} #items .normal, .empty {display:inline;} '
リロードすると、すっきりとした表示になっています。
では、いよいよドラッグ対応にしてみます。
ToDoListComponent >> renderItemsOn: ('rendering'カテゴリ)
renderItemsOn: html html unorderedList id: 'itemList'; script: (html sortable onUpdate: (html request triggerSortable: 'itemList' callback: [:comps | self reorderItems: comps])); with: [self itemComponents withIndexDo: [:each :idx | html listItem passenger: each; with: [html checkbox value: false; callback: [:selected | selected ifTrue: [self selections add: idx]]. html render: each]]]
unorderedList に、id: を送り、'itemList' という名前をつけるようにしました。 さらに script: で、JavaScript となる部分を指定しています。
script: の内容は、主にイベントハンドラの記述です。 まずレンダラに sortable を送り、ソート可能リストを生成します。 次に onUpdate: で順序が変わったときのハンドラを指定します。 Ajax で通信を行うため、レンダラに request を送り、id が 'itemList' の DOM 要素についてコールバックを設定するという書き方になっています(html request triggerSortable:callback:)。
コールバック内では、自身に reorderItems: を送る処理を書いています。 ブロック引数は順番が更新された ToDoItemComponent 群を想定しています。
引数の指定は次の itemComponents のイテレート部分で行っています。 passenger: で ToDoItemComponent のインスタンスを設定することで、コールバックの引数が ToDoItemComponent 群となります。
reorderItems: の中身は単純です。
ToDoListComponent >> reorderItems: ('private'カテゴリ)
reorderItems: newItems itemComponents := newItems. self session currentUser toDoItems: (itemComponents collect: [:each | each item])
並び替えられた ToDoItemComponent が引数としてやってくるので、インスタンス変数 itemCompoents に代入しています。 さらにモデル側に対しても、toDoItems: を送り、ToDo 項目の並び替えを反映させています。 これを行わないとビュー側のみの更新となり、リスト再取得のタイミングで順番が元に戻ってしまうことになります。
では、動作を確認してみましょう。
https://localhost:9090/seaside/toDoList
図では 3 番目にあった「もっと読む」をドラッグして、一番上へと持っていっています。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-Ajax3.st
今回紹介した機能以外にも、script.aculous.us を使うサンプルコードは Seaside に豊富に入っています。 以下の URL にアクセスしてみましょう。
https://localhost:9090/seaside/tests/scriptaculous
実際に動かしながらサンプルを確認できるようになっています。 "View Source" のリンクをクリックすると、lightbox でブラウザが開き、ソースも確認できます。 script.aculous.us でさらに高度なことがしたい人は、参考にするとよいでしょう。
今まで Seaside の URL は一過性で使い捨てられるという解説をしてきました7。 しかし動的な URL だけでは、ブックマークに登録できないなどの不都合が生じることがあります。 永続的な URL(パーマリンク) は Seaside ではどのように実現されているのでしょうか。
例として、ToDo 項目を閲覧するアプリケーションを考えます。 中身は前回作成した ToDo 検索コンポーネント (ToDoSearchComponent) とほぼ同じものですが、パーマリンクをサポートする点が異なります。
パーマリンクをサポートするには、まず URL の書式を考えなければなりません。 今回は、ごくシンプルに、以下のような形にします。
https://localhost:9090/seaside/toDoBrowse/<ユーザ名>
アプリケーション名 (toDoBrowse) の後に、ユーザ名が続きます。 Seaside 側で URL 末尾のユーザ名を解釈し、該当するユーザの ToDo 項目を閲覧できるようにするというわけです。
コンポーネントとして ToDoBrowseComponent を定義しましょう。 項目を絞り込むための matchString に加え、現在のユーザを保持する user という属性を加えました。
WAComponent subclass: #ToDoBrowseComponent instanceVariableNames: 'matchString user' classVariableNames: '' poolDictionaries: '' category: 'ToDo-Component'
「アクセッサの作成」でアクセッサを作り、さらに initialize メソッドも定義しましょう。 ToDoSearchComponent と違い RDB を使わないため、matchString の初期値は '*' です。
ToDoBrowseComponent >> initialize ('initialization'カテゴリ)
initialize super initialize. user := nil. matchString := '*'
続いてレンダリングです。 基本的には前回の使い回しですが、ToDo 項目の絞り込みが、セッションアダプタを経由しない書き方に簡略化されています(self user toDoItems select:の部分)。
ToDoBrowseComponent >> renderContentOn: ('rendering'カテゴリ)
renderContentOn: html "URLの確認" self checkUrl ifFalse: [^html text: 'URLの/userName指定が不正です']. "ToDoItemのテーブル表示" html table with: [ (self user toDoItems select: [:each | self matchString match: each contents]) do: [:each | html tableRow with: [ html tableData: [html text: each contents] ] ] ]. html horizontalRule. "検索条件を入れるフォームの表示" html form: [ html textInput callback: [:v | matchString := v ]; with: self matchString. html submitButton. ]
そして重要なのが、パーマリンクのサポート用にメソッドの先頭で URL の確認を行っているという点です(self checkUrl)。
想定された URL と異なる書式だった場合、通常のレンダリングを行わずに、「URLの/userName 指定が不正です」と警告のメッセージを表示します。
URL の確認を行う checkUrl メソッドは以下のように書けます。
ToDoBrowseComponent >> checkUrl ('private'カテゴリ)
checkUrl self user ifNil: [^false]. ^(self session currentRequest url findTokens: '/') last = self user name
若干複雑に見えますが、基本的には URL の末尾が現在のユーザ名と一致するかをチェックしているだけです。
Web ブラウザから入力した URL は、セッション経由で取得できます(self session currentRequest url)。 これは単なる文字列なので、findTokens: を使って '/' で分割し、さらに last を送って末尾のユーザ名となる部分を取り出しています 8。
user が nil の場合は、こうしたチェックを行うまでもないため、すぐに false を返しています。
では、user はどこで設定するのでしょうか。 ログイン用のコンポーネントなどを介さずに、URL から直接ユーザを特定し、コンポーネントの user 変数にセットしておく必要があります。
実はこうしたときのために、initialRequest: というフックを Seaside は用意しています。 このメソッドはコンポーネントへの最初のアクセス時に、レンダリングよりも前に呼ばれるようになっています。
以下のように initialRequest: をオーバーライドすればよいでしょう。
ToDoBrowseComponent >> initialRequest: ('request processing'カテゴリ)
initialRequest: aRequest | userName | super initialRequest: aRequest. "リクエストからユーザ名を取得" userName := (aRequest url findTokens: '/') last. "ToDoMemoryDBからユーザを名前で検索しuserにセット" self user: (ToDoMemoryDB default users detect: [:each | each name = userName] ifNone:[])
initialRequest: の引数として WARequest のインスタンスがやってくるので、checkUrl と同様に、URL からユーザ名を取り出しています。 次に該当するユーザを ToDoMemoryDB から検索し、見つかった場合は user 変数にセットしておくというわけです。
さて、これで URL からのユーザの特定が可能となりました。 最後に updateUrl: というフックも定義します。
ToDoBrowseComponent >> updateUrl: ('updating'カテゴリ)
updateUrl: aUrl
"URLの末尾にユーザ名を追加"
self user name ifNotNilDo: [:userName | aUrl addToPath: userName]
これはコンポーネントがレスポンスを返すときの URL を加工するためのものです。 ここでは addToPath: を使って、アプリケーション名の後に、現在のユーザ名を加えるようにしています。 こうすることで、ToDoBrowseComponent が絞り込みの結果を返すときなどにも、かならずユーザ名が URL の末尾に入るようになります。
動作を確認してみましょう。 'toDoBrowse' という名前でコンポーネントを登録します。 ワークスペースで下記を実行してください。
ToDoBrowseComponent registerAsApplication: 'toDoBrowse'
まずはユーザ名を指定しないでアクセスしてみます。
https://localhost:9090/seaside/toDoBrowse
initialRequest: で特定のユーザが見つからないため、user の値は nil になります。 このため「URLの/userName指定が不正です」と警告が出ます。
今度は正しいユーザ名を指定してみましょう。
https://localhost:9090/seaside/toDoBrowse/user1
user1 についての ToDo 項目が表示されます。
ではフォームで '*Seaside*' などと入力して絞り込みを行うとどうなるでしょうか。
コンポーネントからのレスポンスなので、セッション ID と継続 ID が URL に付与されます。 updateUrl: をオーバーライドしたため、単に ID がつくだけではなく、URL にユーザ名が加わっていることが確認できるはずです。
https://localhost:9090/seaside/toDoBrowse/user1?_s=1q3K7O8clVGEvy7d&_k=knM7xotF
URL 中のセッション ID や継続 ID は一定時間アクセスがなければ無効になりますが、URL 自体はパーマリンクとして有効です。 上記の URL は本記事を書いている段階で Web ブラウザから貼り付けたものですが、クリックすると user1 の情報にアクセスできます。 これが Seaside によるパーマリンクのサポートです9。
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-Permalink.st
*8 セッション ID(_s=...) や継続 ID(_k=...) は除去された形に整形されています。
*9 もちろん古いセッションは無効となっているので、アプリケーションはそうした場合を考慮して作る必要があります。
アプリケーションができた後は、いよいよデプロイメントに入ります。 ここではデプロイのための Tips をいくつか紹介していきます。
デプロイで、もっとも重要なのは、設定を Deployment モードに切り替えることです。 "Configure" リンクをたどり、設定ツールを開きましょう。 "General" の下にある Deployment Mode を override して、true に変更します。
"Save" を行った後、"New Session" で立ち上げ直すと、ページ下に常に表示されていた、デバッグ用のリンクが非表示となります。
上の図は ToDo アプリで Deployment モードを true にしてみた例です。
なお Deployment モードを true にすると、当然ながら "Configure" リンクは消えてしまうので、再設定を行うには config アプリを起動し、そこから "Configure" で各アプリの設定へとたどっていく必要があります。
config アプリは以下の URL でアクセスできます10。
https://localhost:9090/seaside/config
そのほか各アプリケーションの重要な設定項目としては、"Server" 欄の Server Hostname があります。 これは外部にサービスを公開するときに、グローバルホスト名を指定するためのものです11。
*10 SeasideJOnePlusDB の場合、config のユーザ名/パスワードは admin/seaside に設定してあります。
*11 はじめての Seaside の「運用について」には、画面なしでの起動、Apache との連携の話なども載っています。
"Configure" で表示される画面は、アプリケーションごとにカスタマイズ可能です。 例として、ToDo アプリに confirmOnRemove という新たな設定項目を追加してみます。
まず、設定項目定義のためのコンフィギュレーションクラスを作成します。 WASystemConfiguration というクラスを継承します。
WASystemConfiguration subclass: #ToDoConfiguration instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'ToDo-Component'
次に attributes というメソッドを定義します。 この中で新たに加えたい設定項目を配列の形で返すようにします。
ToDoConfiguration >> attributes ('attributes'カテゴリ)
attributes ^ Array with: (WABooleanAttribute key: #confirmOnRemove group: #todo)
上記のコードの場合、真偽値の値を取る confirmOnRemove という項目を、todo グループとして作成するという指定になっています12。
confirmOnRemove のデフォルト値も定義する必要があります。 単に confirmOnRemove という名前のメソッドを定義して、デフォルト値を返すように書きます。
ToDoConfiguration >> confirmOnRemove ('defaults'カテゴリ)
confirmOnRemove ^true
あとは、このコンフィギュレーションを既存のToDoアプリに追加するだけです。設定ツール上の "Ancestry"欄から ToDoConfiguration を選択して 追加した後 "Save" します。
あるいはワークスペースで以下のように実行してもよいでしょう。
app := WADispatcher default entryPointAt: 'toDoList'.
app configuration addAncestor: ToDoConfiguration new
ToDoListComponent の remove を、コンフィギュレーションの値を見るように変更してみましょう。
ToDoListComponent >> remove ('actions'カテゴリ)
remove
"confirmOnRemoveの値がtrueのときのみ、削除時に確認する"
(self application configuration valueAt: #confirmOnRemove) ifTrue: [
| confirm |
confirm := self lightbox: (ToDoConfirmDialog new messageText: '本当に削除しますか?').
confirm ifFalse: [^self].
].
self selections
do: [:idx | self session removeToDoItem: (self itemComponents at: idx) item].
self initItemList
コンポーネント側からは self application configuration とすると、コンフィギュレーションを取得できます。 設定値は valueAt: <キー> で参照します。
これで confirmOnRemove が true のときのみ、削除の確認を行うようになりました。
ToDoConfiguration をアプリケーションに追加した後には、設定ツールの一番下に "Todo" というグループが表示されるようになります。
ワークスペースから手動で設定値を変えたい場合は以下のようにします。
app := WADispatcher default entryPointAt: 'toDoList'. app configuration valueAt: #confirmOnRemove put: false
confirmOnRemove を true や false に切り替えて、ToDo 項目を削除したときの動きの違いを確認してみてください。
https://localhost:9090/seaside/toDoList
ファイルイン用ソースコードです。 SeasideGo-Lesson-Chap6-Configuration.st
*12 真偽値の他、文字列や数値なども使えます。詳しくは WAConfigurationAttribute のサブクラスをブラウズして下さい。
本連載では Seaside 2.8 を取り扱いましたが、2.9 もリリース間近となっています。 最後に今後の動きを簡単に紹介していきましょう。
Seaside 2.9 の最大の特徴はモジュール化です。 クラスライブラリがより細分化され、必要な機能のみを選択して利用可能になります。 Seaside で重要な役割を果たしていた「継続」も、2.9 からはオプションです。 Ajax の流れを取り入れて、継続を用いない形でのアプリケーション構築も可能となっています。
もちろん継続自体も強化され「部分継続」のサポートにより、さらに軽量化が施されています13。 Web アプリケーションプラットフォームとして、ますます盤石のものになりつつあるといえるでしょう。
2.9 はまだベータ版ですが、下記からダウンロードが可能です。
https://builder.seaside.st/
*13 開発者のブログ Seaside 2.9: Partial Continuation で詳しい解説がされています。
seaBreeze は Web ブラウザから Seaside のコンポーネントを WYSIWYG で直接編集できるフレームワークです。 GUI ビルダーのような感覚で、コンポーネントの配置やイベントハンドラ、プロパティを設定できます。 seaBreeze を使うと Seaside のアプリケーションを今まで以上に迅速に開発できるようになります。
seaBreeze のサイトも seaBreeze 自身で作られています。
https://seabreeze.heeg.de/
まずはトップページにある動画を見てみるとよいでしょう。 基本的な機能がコンパクトに紹介されています。
残念ながら seaBreeze は VisualWorks 版の Seaside のみをサポートしています。 本連載で扱っている Squeak 上での動作には、移植が進んでいるものの、まだ少し時間がかかる模様です14。
興味がある人はぜひ seaBreeze をダウンロードしてお試しください。 非商用の目的であれば自由に使うことができます。
*14 VisualWorks にも非商用版があります。Squeak よりも非常に高速なので、これを機に VisualWorks を入れてみるのも良いでしょう。
GLASS とは GemStone, Linux, Apache, Seaside, and Smalltalk の頭文字を取ったものです。 いわゆるフルスタックの Web アプリケーションフレームワークで、Seaside に、老舗のオブジェクト指向データベースである GemStone をバックエンドとして組み合わせることで、スケーラビリティを持たせています。
GLASS は以下からダウンロードできます。
https://seaside.gemstone.com/
いくつか配布形態がありますが、もっとも試しやすいのは GLASS Virtual Appliance という、VMware イメージ版です。 なお GemStone は大規模向けにチューニングされているため、動作環境は 64 ビットをサポートする CPU 上に限定されます。
GemStone はデータベースというよりは、生きたオブジェクトを共有する「オブジェクト・ベース」と呼ぶべきものです。 Seaside のイメージが、マルチユーザ対応となり、サーバ上でデーモンとして動作するようになっていると考えるとわかりやすいでしょう。
実際 GemStone でプログラミングするということは、サーバ環境にログインして、永続オブジェクトの姿を変えていくことなのです。
既存の Seaside で書かれたプログラムを GemStone 対応にするのは非常に簡単です。 基本的に普段使っているコレクションを永続用コレクションに取りかえるだけですむようになっています。
ここでは最近公開された GLASS 対応の Seaside チュートリアルを紹介しておきます。
https://seaside.gemstone.com/tutorial/
より本格的なドキュメントは下記から参照可能です。
https://seaside.gemstone.com/userguide.html
ソースコードをまとめて取り込めるように、.st ファイルとして置いておきます。 ダウンロードしてお使いください。
「スタイルの適用」
SeasideGo-Lesson-Chap6-CSS1.st
SeasideGo-Lesson-Chap6-CSS2.st
SeasideGo-Lesson-Chap6-CSS3.st
「Ajaxの利用」
SeasideGo-Lesson-Chap6-Ajax1.st
SeasideGo-Lesson-Chap6-Ajax2.st
SeasideGo-Lesson-Chap6-Ajax3.st
「パーマリンクへの対応」
SeasideGo-Lesson-Chap6-Permalink.st
「デプロイに向けて」
SeasideGo-Lesson-Chap6-Configuration.st
6 回にわたる Seaside の連載が終わりました。 基本的なコンポーネントの作成から、データベース接続、Ajax への対応など、内容は多岐にわたりました。 アプリケーションを作るのに必要な内容は、一通り網羅できたといって良いでしょう。
すでに Seaside による革新的な Web アプリケーションが、世界のあちこちで動き始めています15。 これからはぜひ皆さんの手でアプリケーション作りにチャレンジしてみてください。
*15 はじめての Seaside の「システム開発事例」で実際に構築されたサービスの例を見ることができます。
© 2009 Masashi Umezawa |
|