第9章: Behavior
Behavior は Wicket の最も強力な機能のひとつです。 コンポーネントを継承せずに、プラグインのように機能を追加できます。 HTML 属性の操作、AJAX イベントの追加、CSS/JavaScript の注入など、 さまざまな用途に使えます。
Behavior とは
Behavior は、コンポーネントに「後付け」で機能を追加する仕組みです。 デザインパターンでいうと Decorator パターン に相当します。
例えば、ある Label にツールチップを追加したいとします。
Label を継承して ToolTipLabel を作ることもできますが、
Behavior を使えば任意のコンポーネントにツールチップ機能を追加できます。
// コンポーネントに Behavior を追加する基本パターン
Label label = new Label("name", "田中太郎");
label.add(new AttributeModifier("title", "社員名を表示しています"));
label.add(new AttributeModifier("style", "font-weight:bold"));
add(label);
// 結果: <span title="社員名を表示しています" style="font-weight:bold">田中太郎</span>
Behavior の利点:
- 再利用性: 1つの Behavior を任意のコンポーネントに適用できる
- 合成可能: 複数の Behavior を1つのコンポーネントに追加できる
- 関心の分離: コンポーネントのロジックと装飾を分離できる
- 継承不要: コンポーネントのクラス階層を汚さない
Behavior のライフサイクル
Behavior は以下のライフサイクルメソッドを持ちます。 これらをオーバーライドして機能を実装します。
| メソッド | タイミング | 用途 |
|---|---|---|
onComponentTag() |
コンポーネントのタグ出力時 | HTML 属性の追加・変更 |
renderHead() |
ヘッダー出力時 | CSS/JavaScript の追加 |
beforeRender() |
コンポーネント描画前 | 描画前の準備処理 |
afterRender() |
コンポーネント描画後 | 描画後の後処理 |
bind() |
コンポーネントに追加された時 | 初期化処理 |
unbind() |
コンポーネントから削除された時 | クリーンアップ |
AttributeModifier
AttributeModifier は、HTML タグの属性を追加・変更する最も基本的な Behavior です。
Wicket で最も頻繁に使われる Behavior のひとつです。
属性の設定(置換)
// class 属性を設定する
component.add(new AttributeModifier("class", "highlight"));
// style 属性を設定する
component.add(new AttributeModifier("style", "color:red"));
// data 属性を設定する
component.add(new AttributeModifier("data-id", userId));
// 動的な値(モデルを使用)
component.add(new AttributeModifier("title",
new PropertyModel<>(user, "fullName")));
ファクトリメソッド
AttributeModifier には便利なファクトリメソッドが用意されています。
// replace: 既存の値を置換(new AttributeModifier と同じ)
component.add(AttributeModifier.replace("class", "new-class"));
// append: 既存の値に追加(スペース区切り)
component.add(AttributeModifier.append("class", "additional-class"));
// 結果: class="existing-class additional-class"
// prepend: 既存の値の前に追加
component.add(AttributeModifier.prepend("class", "first-class"));
// remove: 属性を削除
component.add(AttributeModifier.remove("style"));
AttributeAppender
AttributeAppender は AttributeModifier.append() と同じ機能のクラスです。
CSS クラスを追加する場合に特に便利です。
// CSS クラスを条件付きで追加
if (isActive) {
component.add(AttributeModifier.append("class", "active"));
}
if (hasError) {
component.add(AttributeModifier.append("class", "error"));
}
// 結果: class="active error"(両条件がtrueの場合)
AjaxEventBehavior
AjaxEventBehavior は、任意の JavaScript イベント(click, mouseover, keydown 等)に
AJAX コールバックを紐づける Behavior です。
これにより、任意のコンポーネントを AJAX 対応にできます。
WebMarkupContainer box = new WebMarkupContainer("clickableBox");
box.setOutputMarkupId(true);
add(box);
box.add(new AjaxEventBehavior("click") {
@Override
protected void onEvent(AjaxRequestTarget target) {
// クリックされた時の処理
clickCount++;
info("ボックスがクリックされました(" + clickCount + "回目)");
target.add(feedbackPanel);
}
});
さまざまなイベントに対応できます:
| イベント名 | 発生タイミング |
|---|---|
"click" | クリック時 |
"dblclick" | ダブルクリック時 |
"mouseover" | マウスオーバー時 |
"mouseout" | マウスアウト時 |
"keydown" | キーダウン時 |
"keyup" | キーアップ時 |
"focus" | フォーカス時 |
"blur" | フォーカスが外れた時 |
OnChangeAjaxBehavior
OnChangeAjaxBehavior は、フォームコンポーネントの値が変わった時に
AJAX コールバックを実行する Behavior です。
連動するドロップダウン(都道府県→市区町村)などの実装に便利です。
// 都道府県の選択に応じて市区町村リストを更新する例
DropDownChoice<String> prefectureChoice =
new DropDownChoice<>("prefecture",
new PropertyModel<>(this, "selectedPrefecture"),
prefectures);
final DropDownChoice<String> cityChoice =
new DropDownChoice<>("city",
new PropertyModel<>(this, "selectedCity"),
new PropertyModel<>(this, "availableCities"));
cityChoice.setOutputMarkupId(true);
prefectureChoice.add(new OnChangeAjaxBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// 都道府県が変わったら市区町村リストを更新
updateCityList(selectedPrefecture);
target.add(cityChoice);
}
});
form.add(prefectureChoice);
form.add(cityChoice);
AjaxFormSubmitBehavior
AjaxFormSubmitBehavior は、指定したイベントが発生した時に
フォームを AJAX で送信する Behavior です。
例えば、Enter キーを押した時にフォームを送信する、といった使い方ができます。
textField.add(new AjaxFormSubmitBehavior(form, "keydown") {
@Override
protected void onSubmit(AjaxRequestTarget target) {
// フォーム送信成功時の処理
target.add(resultLabel);
}
@Override
protected void onError(AjaxRequestTarget target) {
target.add(feedbackPanel);
}
});
ヘッダーへの CSS/JS 追加
Behavior の renderHead() メソッドをオーバーライドすると、
<head> タグに CSS や JavaScript を追加できます。
コンポーネントが使われている時だけ必要な CSS/JS を読み込む場合に有用です。
CSS ファイルの追加
public class HighlightBehavior extends Behavior {
@Override
public void renderHead(Component component,
IHeaderResponse response) {
super.renderHead(component, response);
// CSS ファイルを追加
response.render(CssHeaderItem.forReference(
new CssResourceReference(
HighlightBehavior.class, "highlight.css")));
// インライン CSS を追加
response.render(CssHeaderItem.forCSS(
".highlight { background: yellow; }", "highlight-style"));
}
}
JavaScript ファイルの追加
public class ChartBehavior extends Behavior {
@Override
public void renderHead(Component component,
IHeaderResponse response) {
super.renderHead(component, response);
// JavaScript ファイルを追加
response.render(JavaScriptHeaderItem.forReference(
new JavaScriptResourceReference(
ChartBehavior.class, "chart.js")));
// インライン JavaScript を追加
response.render(OnDomReadyHeaderItem.forScript(
"console.log('DOM ready!');"));
}
}
OnDomReadyHeaderItem は DOM の読み込み完了後に実行されるスクリプトを追加します。
jQuery の $(document).ready() と同等です。
コンポーネントの初期化スクリプトに適しています。
カスタム Behavior の作成
独自の Behavior を作成することで、プロジェクト固有の機能をコンポーネントに追加できます。 以下にいくつかの実践的な例を示します。
例1: ツールチップ Behavior
public class TooltipBehavior extends Behavior {
private final IModel<String> tooltipModel;
public TooltipBehavior(String tooltip) {
this(Model.of(tooltip));
}
public TooltipBehavior(IModel<String> tooltipModel) {
this.tooltipModel = tooltipModel;
}
@Override
public void onComponentTag(Component component,
ComponentTag tag) {
super.onComponentTag(component, tag);
tag.put("title", tooltipModel.getObject());
tag.put("data-toggle", "tooltip");
}
}
// 使い方
label.add(new TooltipBehavior("この項目の説明です"));
例2: 確認ダイアログ Behavior
public class ConfirmBehavior extends Behavior {
private final String message;
public ConfirmBehavior(String message) {
this.message = message;
}
@Override
public void onComponentTag(Component component,
ComponentTag tag) {
super.onComponentTag(component, tag);
tag.put("onclick",
"return confirm('" + message + "');");
}
}
// 使い方: 削除リンクに確認ダイアログを追加
deleteLink.add(new ConfirmBehavior("本当に削除しますか?"));
例3: 条件付き CSS クラス Behavior
public class ConditionalCssBehavior extends Behavior {
private final String cssClass;
private final SerializableBooleanSupplier condition;
public ConditionalCssBehavior(String cssClass,
SerializableBooleanSupplier condition) {
this.cssClass = cssClass;
this.condition = condition;
}
@Override
public void onComponentTag(Component component,
ComponentTag tag) {
super.onComponentTag(component, tag);
if (condition.getAsBoolean()) {
String existing = tag.getAttribute("class");
if (existing == null) {
tag.put("class", cssClass);
} else {
tag.put("class", existing + " " + cssClass);
}
}
}
}
// 使い方: 在庫がない場合に "out-of-stock" クラスを追加
row.add(new ConditionalCssBehavior("out-of-stock",
() -> product.getStock() == 0));
Behavior vs コンポーネント継承
機能を追加する方法として「コンポーネントの継承」と「Behavior の追加」のどちらを選ぶべきでしょうか。
| 観点 | Behavior | コンポーネント継承 |
|---|---|---|
| 適用範囲 | 任意のコンポーネントに適用可能 | 特定のクラス階層に限定 |
| 組み合わせ | 複数の Behavior を同時に適用可能 | Java は単一継承のため1つだけ |
| 独自のマークアップ | 持てない | Panel なら独自の HTML を持てる |
| 向いている場面 | 属性操作、イベント処理、CSS/JS 追加 | 独自 UI を持つ再利用コンポーネント |
判断基準: 独自の HTML マークアップが必要なら Panel(コンポーネント)、 既存のコンポーネントに機能を付加するなら Behavior を使いましょう。 迷ったら Behavior を選ぶ方が柔軟性が高くなります。
組み込み Behavior 一覧
Wicket には多くの組み込み Behavior が用意されています。主要なものをまとめます。
属性操作
| クラス | 説明 |
|---|---|
AttributeModifier | HTML 属性を設定・置換・削除 |
AttributeAppender | HTML 属性に値を追加 |
AJAX 関連
| クラス | 説明 |
|---|---|
AjaxEventBehavior | 任意の JS イベントで AJAX コールバック |
AjaxFormSubmitBehavior | イベント発生時に AJAX フォーム送信 |
AjaxFormChoiceComponentUpdatingBehavior | 選択系コンポーネントの AJAX 更新 |
OnChangeAjaxBehavior | 値変更時の AJAX コールバック |
AjaxSelfUpdatingTimerBehavior | 定期的な AJAX 自動更新 |
AbstractAjaxTimerBehavior | カスタム定期更新の基底クラス |
その他
| クラス | 説明 |
|---|---|
AbstractDefaultAjaxBehavior | カスタム AJAX Behavior の基底クラス |
Behavior | すべての Behavior の基底クラス |
実務でハマりやすい注意点
-
問題Behavior を追加したのに Ajax がまったく反応しない。コード上は正しそうに見えるため、原因に気づきにくい。原因Wicket のバージョン差分でイベント名が変わっており、古い書き方のままになっている。イベントが発火条件に一致していない。対策対象バージョンのイベント名を確認し、
changeなど正しい名称へ統一する。移行時は一覧で見直す。 -
問題同じ操作でも効いたり効かなかったりして、挙動が安定しない。現場では「たまに失敗する」不具合として報告される。原因Behavior 実装だけでなく、DOM構造やJavaScript側に不整合がある。イベント伝播や生成タイミングで取りこぼしが起きる。対策イベント伝播、要素生成タイミング、JavaScript エラーを合わせて確認する。Wicket側とフロント側を分けて切り分ける。
-
問題表示制御まわりが重くなり、画面描画が遅くなる。さらに想定外の再評価で挙動が不安定になる。原因重い判定ロジックを
isVisible()に書いているため、描画中に何度も評価される。副作用がある実装だと特に崩れやすい。対策表示判定はonConfigure()に寄せて1箇所で制御する。副作用のない判定にして再評価コストを抑える。