...

2013.10.17 第2回Wicket講習会資料完全版 - Wicket

by user

on
Category: Documents
231

views

Report

Comments

Transcript

2013.10.17 第2回Wicket講習会資料完全版 - Wicket
Wicketによる
モダンなWebアプリケーション
Wicket-Sapporo 2013-10-17
Hiroto Yamakawa
自己紹介
• 山川広人(@gishi_yama)
• [email protected]
• 大学で教育支援システムの研究開発・運用に従事
• Apache Wicketを使い始めて6年ぐらい
• Wicket大好き
• Wicket-Sapporoコミュニティはじめました
Wicketの概要と
動作の基本
(前回のふりかえり)
• WicketはJavaプログラマがWebアプリケーシ
ョンを快適に開発できる事を狙っている
• HTMLをほぼそのまま使える(wicket:idをつ
けるだけ)
• 2種類のオブジェクト(コンポーネント、モデ
ル)を使って、wicket:idをつけたタグを書き
換えることでWebページを構築する
Wicketでは
HTMLとJavaが一組
<body>
<p wicket:id="label1">message1 is here.</p>
</body>
HTMLでは、
内容を書き換えたいタグに
wicket:id をつける
import org.apache.wicket.markup.html.WebPage;
public class SimplePage extends WebPage {
public SimplePage() {
// コンストラクタ
}
}
Javaでは、
① WebPageのサブクラス
を作成する
// 1. 表示したいデータ(オブジェクト)を用意
String message1 = "Wicket-­‐Sapporoにようこそ!";
// 2. 表示したいオブジェクトをModelオブジェクトでラップ
IModel<String> model1 = new Model<>(message1);
② 表示したいデータを
取り扱うためのModelを
用意する
// 3. Modelを、対応するHTMLのwicket:id用のコンポーネントにセット
Label label1 = new Label("label1", model1);
// 4. コンポーネントをページにadd
add(label1);
③ 表示方法を決める
コンポーネントにModelを
セットし、ページに追加
(add)する
※コンポーネントの第1引数が wicket:id
<body>
<p wicket:id="label1">Wicket-­‐Sapporoにようこそ!</p>
</body>
∼実行結果∼
wicket-idがつけられたタグの
内容が書きかわる
※ wicket:idはdebugモードからdelploymentモードに切り替えると自動的に消える
<body>
<p wicket:id="label1">message1 is here.</p>
</body>
Label(“label1”)
Wicket:idが一致するHTMLタグの値を上書き
getObject
文章表示コンポーネント
オブジェクト
Model
データ取扱オブジェクト
String
“WicketSapporoにようこそ!”
return
<body>
<p wicket:id="label1">Wicket-­‐Sapporoにようこそ!</p>
</body>
入力フォームの
コード例
送信された文字列をコンソールに表示する
FormPage.html
<form wicket:id="form">
<label>名前</label><br />
<input type="text" wicket:id="name"><br />
<label>年齢</label><br />
<input type="text" wicket:id="age"><br />
<input type="submit">
</form>
FormPage.java
public class FormPage extends WebPage {
private IModel<String> nameModel;
private IModel<Integer> ageModel;
public FormPage() {
nameModel = Model.of("");
ageModel = Model.of(0);
Form<Void> form = new Form<Void>("form") {
@Override
protected void onSubmit() {
super.onSubmit();
System.out.println(nameModel.getObject());
System.out.println(ageModel.getObject());
}
};
add(form);
form.add(new TextField<>("name", nameModel));
form.add(new TextField<>("age", ageModel));
}
}
public class Bean implements Serializable {
private String name;
private int age;
Modelを変えると
@Override
データの取り扱い
public String toString() {
return "[name : " + name + "\nage : " + age + "]";
方法を変えられる
}
//アクセッサは省略
public class FormPage extends WebPage {
}
public FormPage() {
前ページの
Form.javaを
Modelを変えて
書き換えたもの
Form<Bean> form = new Form<Bean>(
"form", new CompoundPropertyModel<>(new Bean())) {
@Override
protected void onSubmit() {
super.onSubmit();
System.out.println(getModelObject().toString());
}
};
add(form);
form.add(new TextField<>("name"));
form.add(new TextField<>("age"));
}
}
今日の内容
• Stateful
• Clean URL
• Ajax
• Session
StateFul
Wicketでは、LinkやFormを利用した
ページの状態を覚えている
ステートフル!
Page
1
Link Page
2
Link
Page
3
History Back × 2
Link
Page
4
例)前の加算結果に新しい値を足していく機能
885
1791
ブラウザの戻る2回
885
最初のページの状態
(計算結果)を
自動的に保持している
Link
new Link<T>("id", IModel<T>) {
@Override
public void onClick() {
// リンクやボタンが押された時の処理
}
};
リンクやボタン用のコンポーネント
onClick() メソッドの中で
setResponsePage(WebPageのサブクラスのインスタンス);
の様に書くと、インスタンスを次ページとして遷移させる(遷移先もステートフル)
Form
new Form<T>("id", IModel<T>) {
@Override
public void onSubmit() {
// Submitボタンがクリックされた時の処理。
}
};
Formタグの大枠となるコンポーネント
ページの遷移もさせたい場合はLinkと同様に setResponsePageメソッドを書く。
Submitボタンに特殊なものを使いたい時は、Buttonコンポーネントなどを利用できる。
LinkやFormが貼り付けられているページや遷移した先のペ
ージのURLにつくPageIdごとに、ページの状態が自動的に
保存される。保存先はSessionとディスク。
PageIdを指定すると、そのときの状態のページが復元され
る(バージョン管理的)。
PageIdは初期設定では連番だが、推測されたくない時は
URLのcrypt暗号化やPageIdの乱数化が有効。
Clean URL
Clean URL
?xxx=yyy...
の様なURL(クエリ文字列)を
/xxx/yyy/...
の様なURL(クエリ文字列)に
WicketでClean URLな
クエリ文字列を使うには、
1.ブックマーク可能リンクを使う
(BookmarkablePageLink)
2.URLとクエリ文字列の形式を登録
する(MountedMapper)
BookmarkablePageLink
new BookmarkablePageLink<T>("id", クラス);
ブックマーク可能なURLでページを遷移する
Linkコンポーネント
PageParameters params = new PageParameters();
params.add("param1", "1000");
add(new BookmarkablePageLink<Void>("id", クラス, params));
クエリパラメータを渡す時は、PageParametersオブジェクトを用意し、
BookmarkablePageの第3引数に渡す
//通常のリンク
add(new Link<Void>("link") {
@Override
public void onClick() {
setResponsePage(new IdReceiptPage());
}
});
//ブックマーク可能リンク
add(new BookmarkablePageLink<Void>("bpLink", IdReceiptPage.class));
通常のリンクで遷移した IdReceiptPage ページのURL
http://localhost:8080/wicket_workshop/ws01/wicket/page?1
ブックマーク可能リンクで遷移した IdReceiptPage のURL
http://localhost:8080/wicket_workshop/ws01/wicket/bookmarkable/
org.wicket_sapporo.workshop01.page.bookmarkable.IdReceiptPage
http://localhost:8080/wicket_workshop/ws01/wicket/bookmarkable/
org.wicket_sapporo.workshop01.page.bookmarkable.IdReceiptPage
ブックマーク可能リンクでは、
クラスのパッケージ名+クラス名がURLとして表示されるが....
public class WS01Application extends WebApplication {
@Override
protected void init() {
super.init();
// ...中略
mount(new MountedMapper("/id_receipt", IdReceiptPage.class));
}
}
WebApplication のサブクラスで
MountedMapper オブジェクトを使えば...
http://localhost:8080/wicket_workshop/ws01/id_receipt
シンプルなURLになるよう設定できる
MountedMapper
mount(new MountedMapper("/PathToPage", クラス));
あるクラスにブックマーク可能なURLをマッピングする
クエリ文字列の形式も設定できる
mount(new MountedMapper("/Path", クラス, new UrlPathPageParametersEncoder()));
第3引数に UrlPathPageParametersEncoder を渡すと、
Clean URL の形式でクエリ文字列を設定できるようになる
mount(new MountedMapper("/Path/${param1}", クラス);
パスに ${パラメータ名} を加えると、その部分をクエリ文字列(の値)として扱う
利用例
WebApplicationのサブクラス#init()
mount(new MountedMapper("/id_receipt", IdReceiptPage.class,
new UrlPathPageParametersEncoder()));
遷移元ページクラスのコンストラクタ
PageParameters params = new PageParameters();
params.add("param1", "1000");
add(new BookmarkablePageLink<Void>("id", IdReceiptPage.class, params));
ページ遷移時(IdReceiptPage.class)のURL
http://localhost:8080/wicket_workshop/ws01/id_receipt/param1/1000
利用例
遷移先ページクラスのコンストラクタ
public IdReceiptPage(PageParameters params) {
if (params != null) {
String param1 = params.get("param1").toString("パラメータなし");
String param2 = params.get("param2").toString("パラメータなし"));
}
// 以下略
クエリパラメータはコンストラクタの引数にPageParametersオブジェクトとして
渡されるので、パラメータ名を指定して値を取得する。
なお、toStringメソッドの引数は、パラメータが無い時の初期値。
Ajax
Wicketにはサーバと通信して
ページを部分的に更新するための
Ajax機能が組み込まれている
WicketでAjax機能を
利用する方法
• Ajaxを内蔵したコンポーネントを使う
• Ajaxを内蔵したビヘイビアを使う
ビヘイビア:コンポーネントにaddして使う後付け命令
Ajaxを利用した
コンポーネントの例
• AjaxLink, AjaxButton
リンクやボタンを押すとコンポーネントを更新
(AjaxButtonはFormの送信も)
• IndicatingAjaxLink, IndicatingAjaxButton
AjaxLink,AjaxButtonにインジケータ(ぐるぐる)
を表示
• AjaxEditableLabel,
AjaxEditableMultiLineLabel
その場で編集できるラベル
Ajaxを利用した
コンポーネントの例
• AutoCompleteTextField
入力値を補完するテキストフィールド
• LazyLoadPanel
パネルが表示されるまでインジケータ(ぐるぐる)表示
• ModalWindow
画面内の子ウィンドウ
• NestedTree
ディレクトリツリー
などなど
AjaxLink
new AjaxLink<Void>("link") {
@Override
public void onClick(AjaxRequestTarget target) {
// AjaxLinkがクリックされた時の処理
}
}
リンクやボタン用のクリック時に
Ajaxで処理をするコンポーネント
利用例
リンクをクリックするたびに
表示・非表示
ページのHTML の一部
<p><a wicket:id="link">リンク</a></p>
<div wicket:id="green"><p>上のリンクを押すと表示が切り替わります</p></div>
ページクラスの
コンストラクタ
final WebMarkupContainer green = new WebMarkupContainer("green") {
@Override
protected void onInitialize() {
super.onInitialize();
// Ajaxの更新対象に設定し、さらに非表示の際も更新対象とする
setOutputMarkupId(true);
setOutputMarkupPlaceholderTag(true);
}
@Override
protected void onConfigure() {
super.onConfigure();
// コンポーネントを更新すると、現在の表示状態を反転する
setVisible(!isVisible());
}
};
add(green);
add(new AjaxLink<Void>("link") {
@Override
public void onClick(AjaxRequestTarget target) {
// green変数のコンポーネントを更新する
target.add(green);
}
});
Ajaxの更新対象にする上
で重要なメソッド
•
Component#onInitialize()
コンポーネントのインスタンス化の時に実行されるメソッド
コンポーネントの初期設定などを記述する
•
Component#onConfigure()
コンポーネントの更新のたびに実行されるメソッド
コンポーネントの更新時にあわせた設定変更などを記述する
•
Component#setOutputMarkupId(bolean)
コンポーネントをAjaxの更新対象にする(JavaScriptのフック用idを生成)
•
Component#setOutputMarkupPlaceholderTag(boolean)
コンポーネントが非表示の時にもJavaScriptがフックできるようにタグを残す
(非表示にならなければ不要)
•
AjaxRequestTarget#add(Component)
引数に与えられたComponentをAjaxで更新する
LazyLoadPanel
new AjaxLazyLoadPanel("id") {
@Override
public Component getLazyLoadComponent(String id) {
// 表示されるPanelを生成する処理
}
};
表示されるPanelの準備ができるまで
インジケータ(ぐるぐる)を表示する
コンポーネント
利用例
内容が表示されるまで
インジケータが代わりに表示される
ページのHTML の一部
<div wicket:id="lazyLoad"></div>
ページクラスのコンストラクタ
add(new AjaxLazyLoadPanel("lazyLoad") {
@Override
public Component getLazyLoadComponent(String id) {
return new DatePrintPanel(id);
}
});
パネルのHTML の一部
<wicket:panel>
<p wicket:id="date"></p>
</wicket:panel>
パネルクラス
public class DatePrintPanel extends Panel {
public DatePrintPanel(String id) {
super(id);
add(new DateLabel("date", Model.of(new Date()), new PatternDateConverter("yyyy/MM/dd HH:mm:ss", true)));
}
}
Ajaxを利用した
ビヘイビアの例
• AbstractAjaxTimerBehavior
AjaxSelfUpdatingTimerBehavior
定期的な処理の実施
• AjaxEventBehavior
JavaScriptのイベント時に処理を実施
Ajaxを利用した
ビヘイビアの例
• AjaxFormSubmitBehavior
AjaxFormValidationBehavior
Form用のComponentに対して、JavaScriptのイベン
ト時にFormをSubmitした上での処理
• AjaxFormComponentUpdatingBehavior
AjaxFormChoiceComponentUpdatingBehavior
OnChangeAjaxBehavior
Form用のComponentに対して、JavaScriptのイベン
ト時にコンポーネントのModelを更新した上での処理
AjaxSelfUpdating
TimerBehavior
new AjaxSelfUpdatingTimerBehavior(Duration);
指定されたタイミング(Duration)ごとに、
コンポーネントを更新するビヘイビア
add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(1)));
add(new AjaxSelfUpdatingTimerBehavior(Duration.minutes(15)));
利用例
1秒ごとに現在時刻をカウントアップする
ページのHTML の一部
<p>現在時刻:<span wicket:id="clock"></span></p>
ページクラスのコンストラクタ
IModel<Date> clockModel = new AbstractReadOnlyModel<Date>() {
@Override
public Date getObject() {
return new Date();
}
};
add(new DateLabel("clock", clockModel, new PatternDateConverter("yyyy/MM/dd HH:mm:ss", true)) {
@Override
protected void onInitialize() {
super.onInitialize();
add(new AjaxSelfUpdatingTimerBehavior(Duration.seconds(1)));
}
});
AjaxFormComponent
UpdatingBehavior
new AjaxFormComponentUpdatingBehavior("JavaScriptイベント") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// JavaScriptイベントが発生した時の処理
}
};
JavaScriptイベントが発生したときに
onUpdateメソッドの内容を実行するビヘイビア
利用例
選択されたものがAjaxで画面に表示される
ページのHTML の一部
<form wicket:id="form">
<select wicket:id="dropDown"></select>
<p>えらんだもの:<span wicket:id="choiced"></span></p>
</form>
ページクラスのコンストラクタ 途中まで
// 選択肢のモデルを用意.
IModel<List<String>> dropDownModel = new ListModel<>(Arrays.asList("札幌", "東京", "大阪", "福岡"));
// 洗濯結果を取り扱うモデルを用意.
IModel<String> choicedModel = new Model<String>(null);
Form<Void> form = new Form<Void>("form");
add(form);
final Label choiced = new Label("choiced", choicedModel) {
@Override
protected void onInitialize() {
super.onInitialize();
setOutputMarkupId(true);
}
};
form.add(choiced);
次スライドへ
ページクラスのコンストラクタ 続き
// ドロップダウン形式のコンポーネントを用意.
form.add(new DropDownChoice<String>("dropDown", choicedModel,
dropDownModel) {
@Override
protected void onInitialize() {
super.onInitialize();
add(new AjaxFormComponentUpdatingBehavior("change") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// choiced 変数のコンポーネントを更新
target.add(choiced);
}
});
}
});
Ajaxを利用した
コンポーネント・ビヘイビアの例
公式のサンプル
http://www.wicket-library.com/wicket-examples-6.0.x/ajax/
click!
Ajax機能を利用しているページでは、
デバッグウィンドウで処理状況を確認できる
アプリケーションをdeploymentモードで起動すると、
このウィンドウは表示されなくなる
Session
Wicketの内部ではSessionが使われている
(例:ステートフル処理)
個人認証など独自のSession管理を行うには
1.独自のSessionクラスをWebSessionの
サブクラスとして作成
2.WebApplicationのサブクラスで、独自
のSessionクラスを利用するように設定
独自のセッションクラスを作成(WebSessionクラスのサブクラスにする)
public class WS01Session extends WebSession {
public WS01Session(Request request) {
super(request);
}
public static WS01Session get() {
return (WS01Session) Session.get();
}
}
Sessionを取得するための static メソッドを必ず作成する!
Wicketではページ(コンポーネントやモデル)の状態の保持にSessionを利用する。
そのため、変数にSessionを格納するとSessionが循環して参照されてしまう恐れがあ
る(保持された場合は StackOverflowError が発生)。
このためWicketでSessionを利用する場合は変数に格納せず、上記の様な static メソ
ッドを作った上で、使うたびごとに WS01Session.get(); のように実行する。
WebApplication クラスのサブクラスで、独自のSessionを利用するように設定
public class WS01Application extends WebApplication {
// 中略
@Override
public Session newSession(Request request, Response response) {
// このアプリケーション用に独自に拡張したSessionを作成.
return new WS01Session(request);
}
}
実例
サインアウト
サインイン
未サインイン
リダイレクト
SimpleSignInPage.html の一部
<form wicket:id="form">
<label>ユーザID : </label>
<input type="text" wicket:id="userId">
<label>パスフレーズ : </label>
<input type="password" wicket:id="passphrase">
<br><button type="submit">サインイン</button>
</form>
SignedPage.html の一部
<p><span wicket:id="userId"></span>さんがサインインしました!</p>
<p><a wicket:id="signOut">サインアウト</a></p>
WS01Session.java 途中まで
public class WS01Session extends WebSession {
private boolean signed;
private boolean userId;
public WS01Session(Request request) {
super(request);
signed = false;
userId = null;
}
public static WS01Session get() {
return (WS01Session) Session.get();
}
public boolean isSigned() {
return signed;
}
次スライドへ
WS01Session.java つづき
public boolean signIn(String userId, String passphrase) {
if (userId != null && passphrase != null) {
if (userId.equals(passphrase)) {
replaceSession(); // Session Fixation対策
this.signed = true;
this.userid = userId;
}
}
return signed;
}
public void signOut() {
invalidate();
}
public String getUserId() {
return userId != null ? userId : "不明";
}
}
Webアプリケーションのサブクラスへの設定は前述と同様のため、省略する。
SimpleSignInPage.java
public class SimpleSignInPage extends WS01TemplatePage {
private String userId;
private String passphrase;
public SimpleSignInPage() {
// ページのフィールド変数とコンポーネントを関連づける CompoundPropertyModel を用意.
// ログインなどのステートフルにしたくない部分には Stateless コンポーネントを利用.
StatelessForm<SimpleSignInPage> form = new StatelessForm<SimpleSignInPage>("form", new CompoundPropertyModel<>(this)) {
@Override
protected void onSubmit() {
super.onSubmit();
if (WS01Session.get()).signIn(userId, passphrase) {
setResponsePage(SignedPage.class);
}
}
};
add(form);
form.add(new RequiredTextField<String>("userId");
form.add(new PasswordTextField("passphrase");
}
}
SignedPage.java
public class SignedPage extends WS01TemplatePage {
public SignedPage() {
this.add(new Label("userId", WS01Session.get().getUserId()));
// ログアウトなどのステートフルにしたくない部分には Stateless コンポーネントを利用.
this.add(new StatelessLink<Void>("signOut") {
@Override
public void onClick() {
WS01Session.get().signOut();
// サインアウトしたら強制的にログイン画面へ.
throw new RestartResponseException(SimpleSignInPage.class);
}
});
}
@Override
protected void onInitialize() {
super.onInitialize();
if (!WS01Session.get().isSigned()) {
// ページの生成時にサインインしてない場合は強制的にログイン画面へ.
throw new RestartResponseException(SimpleSignInPage.class);
}
}
}
より利便性の高い認証機構は
Wicket-Auth-Rolesなどを利用
Apach Shiro も便利の様です
付録
Abstract
ReadOnlyModel
IModel<T> model = new AbstractReadOnlyModel<T>() {
@Override
public T getObject() {
// コンポーネントから呼び出される度に実行する
// オブジェクトの準備処理
}
};
コンポーネントにデータを要求される度に
オブジェクトを生成して返すModel
DateLabel
new DateLabel("id", IModel<Date>, パターン);
日付をパターンの通りに整形して表示する
DateLabel dl = new DateLabel("clock", clockModel, new PatternDateConverter("yyyy/MM/dd HH:mm:ss", true);
表示形式は PatternDateConverter
もしくは StyleDateConverter で設定する
Panel
<!DOCTYPE html>
<html lang="ja" xmlns:wicket="http://wicket.apache.org">
<head>
<meta charset="UTF-­‐8" />
</head>
<body>
<wicket:panel>
<!-­‐-­‐ ひとまとめにするコンポーネント用のタグ -­‐-­‐>
</wicket:panel>
</body>
</html>
SamplePage.html
public class SamplePanel extends Panel {
public SamplePanel(String id) {
super(id);
// ひとまとめにするコンポーネントの準備
}
}
SamplePage.java
Panelコンポーネントは、複
数のコンポーネントをひとま
とめにしたパーツとして扱う
ことができる
Pageの<div>タグなどに
addすると、<wicket:panel>
タグの中身がdiv要素の中身
としてページに反映される
複数回使い回すページ部分や、
NestedForm、
LazyLoadPanelを利用して遅延読込を
させたい部分などに使う
Fly UP