Wicket7のComponent queueingを試してみた

## はじめに

Apache Wicket 7.0.0 のリリースが目前に迫っています。楽しみですね。

ところで、以前のマイルストーンリリースの時に、

<blockquote class="twitter-tweet" data-conversation="none" lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/gishi_yama">@gishi_yama</a> 14章のComponent queueingが新しく追加されたんですか。コンポーネントのバインドミスが減るみたいで嬉しいんですがどうなんでしょうね</p>&mdash; きゃべ (@kyabe) <a href="https://twitter.com/kyabe/status/600534206901452800">2015, 5月 19</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

という話があったのを思い出したので、[Component queueing](https://ci.apache.org/projects/wicket/guide/7.x/guide/single.html#componentQueueing)を試してみました。
結論から言うと、wicket:idが被らなければとても良い感じです。

## Component#queue(Component)

Formをもつ、以下の様なHTMLをWicketで制御したいとします。

```html:HomePage.html（Body内を抜粋）
<body>
  <div>
    <div wicket:id="feedback"></div>
    <form wicket:id="form">
      名前：<input type="text" wicket:id="name" /><br />
      番号：<input type="text" wicket:id="index"/><br /> 
      <input type="submit" />
    </form>
    <h1 wicket:id="result"></h1>
  </div>
</body>
```

このページを Wicket 6 までの作り方で構築する場合、HomePage.javaでは

```java:HomePage.java（の一部。Wicket6の場合）
Form<Void> form = new Form<Void>("form") {
  // -- snip --
}
add(form);
form.add(new TextField<String>("name", nameModel));
form.add(new TextField<String>("index", nameModel));
add(new Label("result", resultModel));
```

といった様に、`<form>`と`<input>`（`Form`と`TextField`）の階層構造、つまり**HTML上のwicket:idの階層構造をもとに、Javaコード上でも同じ階層構造となるように意識して親子のコンポーネントをaddする**必要がありました。

Wicket 7 から採用されるComponent queueingでは、**wicket:idが重複しない限り**、

```java:HomePage.java（の一部。Wicket7の場合）
queue(new Form<Void>("form") {
  // -- snip --
});
queue(new TextField<String>("name", nameModel));
queue(new TextField<String>("index", nameModel));
queue(new Label("result", resultModel));
```

といった様に、wicket:idの階層を意識せずにコンポーネントを準備できるようになります。

よりすっきりとしたコードになって気持ちいいですね。

## wicket:idが重複したら？

ページ内でwicket:idが重複してしまった場合は、従来通りwicket:idの階層構造にもとづいてqueueメソッドを使います。

たとえば、上記のHomePage.htmlの`"result"`が`"name"`というwicket:idだったとしたら、

```java
Form<Void> form = new Form<Void>("form") {
  // -- snip --
});
queue(form);
form.queue(new TextField<String>("name", nameModel));
form.queue(new TextField<String>("index", nameModel));
queue(new Label("name", resultModel));
```

というように、従来と同じ書き方（addがqueueに変わっただけ）になってしまいます。

ここだけを注視すると「addでいいんじゃね？」という声もわき上がってきそうですが...（注1）

一部を除いて**Javaコードを書くときにHTML（wicket:id）の階層を意識しなくてもよい**という開放感が、現状のqueueの嬉しい所ではないでしょうか。

## おわりに

個人的にはWicketのテストケースの様に 

```java
queue(new TextField("form:name", nameModel));
```

みたいに書けるとより気持ちいいなーと思いますが、残念ながらこれはできません（注2）。そのため、全てのコンポーネントをフラットにqueueで追加するためには、たとえば、アンダースコアつなぎのユニークなwicket:idを考えるとかいう前提の元になっちゃいそうです（それはそれで悪手な気が...）。

なので、Wicket 7 を採用する場合は、

- add原理主義
- queueを受け入れ、原則はqueueで、どうしても重複する場合は階層構造前提で

というどちらかのやり方で統一するのが妥当かなと思います。

最後に、上のHomePage.htmlに対してWicket7で作ってみたHomePage.javaの内容を記載しておきます（動作確認優先で書いていますので、Modelの使い方などはご容赦ください）。

```java:HomePage.java
public class HomePage extends WebPage {

  public HomePage() {
    IModel<String> nameModel = Model.of("");
    IModel<Integer> indexModel = Model.of(1);
    IModel<String> resultModel = Model.of("");

    queue(new FeedbackPanel("feedback"));

    queue(new StatelessForm<Void>("form") {
      @Override
      protected void onSubmit() {
        super.onSubmit();
        List<String> omikujis = Stream.of("大吉", "中吉", "小吉", "吉", "凶")
			          .collect(Collectors.toList());
        Collections.shuffle(omikujis);
        String result = nameModel.getObject() 
	                  + "さんの運勢は..."
	                  + omikujis.get(indexModel.getObject() - 1);
        resultModel.setObject(result);
      }
    });

    queue(new RequiredTextField<String>("name", nameModel) {
      @Override
      protected void onInitialize() {
        super.onInitialize();
        add(StringValidator.lengthBetween(1, 10));
        add(new HTML5Attributes());
        setLabel(Model.of("名前"));
      }
    });

    queue(new RequiredTextField<Integer>("index", indexModel, Integer.class) {
      @Override
      protected void onInitialize() {
        super.onInitialize();
        add(RangeValidator.range(1, 5));
        add(new HTML5Attributes());
        setLabel(Model.of("番号"));
      }
    });

    queue(new Label("result", resultModel) {
      @Override
      protected void onConfigure() {
        super.onConfigure();
        setVisible(!Objects.equals(getDefaultModelObjectAsString(), ""));
      }
    });
  }
}
```

ぜひ皆さんも Wicket 7 で楽しいコンポーネントキューイング生活を送ってみませんか。

----
- 注1）初めてユーザーガイドを読んだ時は、私も「addでいいんじゃね...」って思いました。
- 注2）wicket:idでは`:`が禁則文字として扱われていたりします。form:nameの様に階層構造を指定できるのは、Wicketが自動的に生成するときか、テストケースの時だけのようです。