2018年以降の記事はGitHub Pagesに移行しました

Wicketでn行m列で折り返すリストを作る

 Wicket本が未だに入荷しないので一つ書く。Wicket奮闘記第二弾。困り度的には、最悪1列表示で妥協すれば良かったのでそんなに高くない。

 ただのリストじゃなくて、要素が何個か横に続き、n個で折り返す……というサンプル。イメージとしては、mixiのマイミクとかマイコミュニティみたいな感じかなぁ。相変わらずバージョンは1.3なのです。

 こんな感じ。横に2つで折り返し。

 どういう風に実現するかすごい困ったけれど、調べてみるとGridViewというコンポーネントがあったのでこれは使えそうだと。以下Javaソース。

//	テーブルのcolspanに設定する数
	private int column;
	private List<SampleObjectModelBean> selected;
	
	public SampleGridView() {
//		行の設定
		column = 2;
//		DBから何らかのリスト一覧を持ってきたという体で
		final List<SampleObjectModelBean> list = GenerateBeanUtility.GenerateSampleObjectModelBean();
//		自分が取得している項目という体で
		selected = new ArrayList<SampleObjectModelBean>();
		selected.add(list.get(2));
		selected.add(list.get(5));
		
//		DataProvider 規則的に回すもの
		IDataProvider dataProvider = new IDataProvider() {
			private static final long serialVersionUID = -9120134891423038532L;
			public Iterator<SampleObjectModelBean> iterator(int first, int count) {
				return list.iterator();
			}
			public int size() {
				return list.size();
			}
			public IModel model(Object object) {
				return new Model((Serializable)object);
			}
			public void detach() {
			}
		};
		
		final Form form = new Form("form");
		this.add(form);

		final CheckGroup checkGroup = new CheckGroup("checkGroup", new PropertyModel(this, "selected"));
		form.add(checkGroup);

		final WebMarkupContainer colspanWMC = new WebMarkupContainer("colspanWMC");
//		テーブルのcolspan属性を動的にしてやる
		colspanWMC.add(new SimpleAttributeModifier("colspan", String.valueOf(column)));
		checkGroup.add(colspanWMC);

		final GridView gridView = new GridView("rows", dataProvider) {
			private static final long serialVersionUID = 3658408852637870671L;
//			ListViewと同様のpopulateItem
			@Override
			protected void populateItem(Item item) {
				SampleObjectModelBean bean = (SampleObjectModelBean)item.getModelObject();
				item.add(new Check("check", item.getModel()));
				item.add(new Label("value", bean.getName()));
			}
//			空だった場合
			@Override
			protected void populateEmptyItem(Item item) {
				item.add(new Check("check").setVisible(false));
				item.add(new Label("value", "Empty"));
			}
		};
//		カラムの数をセット
		gridView.setColumns(column);
		checkGroup.add(gridView);

		final WebMarkupContainer listWMC = new WebMarkupContainer("listWMC");
		listWMC.setOutputMarkupId(true);
		this.add(listWMC);

//		選択したものを表示
		ListView listView = new ListView("resultList", selected) {
			private static final long serialVersionUID = 2797947925339607450L;
			@Override
			protected void populateItem(ListItem listItem) {
				SampleObjectModelBean bean = (SampleObjectModelBean)listItem.getModelObject();
				listItem.add(new Label("name", bean.getName()));
				listItem.add(new Label("outline", bean.getOutline()));
			}
		};
		listWMC.add(listView);

//		選択決定ボタン
		AjaxButton button = new AjaxButton("button") {
			private static final long serialVersionUID = 7635585733674755967L;
			@Override
			protected void onSubmit(AjaxRequestTarget target, Form form) {
				target.addComponent(listWMC);
			}
		};
		form.add(button);
		
//		列を増やしてみる
		AjaxLink incLink = new AjaxLink("incLink") {
			private static final long serialVersionUID = 7734634038402851693L;
			@Override
			public void onClick(AjaxRequestTarget target) {
				if (column < list.size()) {
					column++;
				}
				colspanWMC.add(new SimpleAttributeModifier("colspan", String.valueOf(column)));
				gridView.setColumns(column);
				target.addComponent(form);
			}
		};
		this.add(incLink);
		
//		列を減らしてみる
		AjaxLink decLink = new AjaxLink("decLink") {
			private static final long serialVersionUID = -3611671121498659140L;
			@Override
			public void onClick(AjaxRequestTarget target) {
				if (column > 1) {
					column--;
				}
				colspanWMC.add(new SimpleAttributeModifier("colspan", String.valueOf(column)));
				gridView.setColumns(column);
				target.addComponent(form);
			}
		};
		this.add(decLink);
	}
	
	public List<SampleObjectModelBean> getSelected() {
		return selected;
	}
	public void setSelected(List<SampleObjectModelBean> selected) {
		this.selected = selected;
	}

 で、html。

	<h1>GridViewを用いてテーブルをn列で折り返した選択フォーム(例:mixiのコミュニティ一覧みたいな)</h1>
	<form wicket:id="form">
		<input type="submit" wicket:id="button" />
		<table cellspacing="0" cellpadding="2" border="1">
			<span wicket:id="checkGroup">
				<tr>
					<th wicket:id="colspanWMC">なんとか一覧</th>
				</tr>
				<tr wicket:id="rows">
					<td wicket:id="cols">
						<input type="checkbox" wicket:id="check" />
						<span wicket:id="value" />
					</td>
				</tr>
			</span>
		</table>
	</form>
	<h1>(おまけ)列を動的に変えてみる</h1>
	<ul>
		<li><a href="#" wicket:id="decLink">列減らす</a></li>
		<li><a href="#" wicket:id="incLink">列増やす</a></li>
	</ul>
	<h1>選択したもの</h1>
	<span wicket:id="listWMC">
		<dl wicket:id="resultList">
			<dt wicket:id="name"></dt>
			<dd class="recital" wicket:id="outline"></dd>
		</dl>
	</span>

 列を変えるボタンのソースとかはおいといて……、とりあえず

 の画面は実現できます。普通のListViewでコレクションを表示していくのと違う点は、

    1. ListViewがGridView
        • 表示するオブジェクトがModel(かList)ではなく、DataProvider
        • populateEmptyItemの処理
        • カラム数をセット
    2. wicket:idでちょこっと

 このくらいです。

 まず、ListViewがGridView。これはそういうコンポーネントがあるのかぁ的な感じでゴリゴリ変えていきます。

 次に、GridViewに変えた事で引数にModelとかListが使えなくなりました……。代わりにDataProviderというものを突っ込んでやらないといけないらしい……。DataProviderはこんな感じ。listはDBから取得してきたという体で生成した選択項目全部のリストです。

		IDataProvider dataProvider = new IDataProvider() {
			private static final long serialVersionUID = -9120134891423038532L;
			public Iterator<SampleObjectModelBean> iterator(int first, int count) {
				return list.iterator();
			}
			public int size() {
				return list.size();
			}
			public IModel model(Object object) {
				return new Model((Serializable)object);
			}
			public void detach() {
			}
		};

 とりあえずこんな感じで動きました。iteratorメソッドには引数が二つあったりするんですが、次回は使います。今回は使わなくても動いた……。*1

 これで、回すのに必要なものは揃ったので、populateItemをオーバライドしますが、populateItemとは別にpopulateEmptyItemというメソッドもオーバライドしないといけません。理由は後述。

 次にテーブルを何列で折り返したいかをsetColumnsメソッドで指定します。今回は頭の方で2に設定しているので、n行2列のテーブルに出来るはずです。しかし、列を指定した事でテーブルに空白が出来る可能性が起こってしまいます。2列のテーブルなら、リストが奇数の場合最後の行が1列しか埋まりません;

 ここでpopulateEmptyItemの出番です。テーブルがまだ全部埋まっていないのに要素が終わってしまった場合、これが呼ばれます。

 Emptyと表示しているところがpopulateEmptyItemが呼ばれている箇所です。めでたしめでたし。

 最後にwicket:id。

		final GridView gridView = new GridView("rows", dataProvider) {
			private static final long serialVersionUID = 3658408852637870671L;
//			ListViewと同様のpopulateItem
			@Override
			protected void populateItem(Item item) {
				SampleObjectModelBean bean = (SampleObjectModelBean)item.getModelObject();
				item.add(new Check("check", item.getModel()));
				item.add(new Label("value", bean.getName()));
			以下略

 JavaWicket:idを生成しているのは、

    • GridViewのrows
        • populateItem内のCheckのcheck
        • populateItem内のLabelのvalue

 の三つなんですが、この三つをListView的な感じでバインドしていっても、

WicketMessage: Unable to find component with id 'check' in [MarkupContainer [Component id = 1, page = sampleWicket.view.gridView.SampleGridView, path = 2:form:checkGroup:rows:1.Item, isVisible = true, isVersioned = false]]. This means that you declared wicket:id=check in your markup, but that you either did not add the component to your page at all, or that the hierarchy does not match.

 checkがない! と怒られてしまいます。

 なんで>< と調べてみると、どうも、

				<tr wicket:id="rows">
					<td wicket:id="cols">
						<input type="checkbox" wicket:id="check" />
						<span wicket:id="value" />
					</td>
				</tr>

 rows(=GridView)の下にcolsというidを振らないといけないらしい。GridViewクラスの中にも

		Item rowItem = newRowItem(newChildId(), row);
		RepeatingView rowView = new RepeatingView("cols");
		rowItem.add(rowView);
		add(rowItem);

 こんな記述があって、どうもcolsを振っているらしい! 振れば動く!

 最後はらしいらしいが続いてますが、一応これでmixiのマイピクみたいなテーブルも作れそうです。

 この記事を書くに当たって、行と列の覚え方で行と列のわかりやすい覚え方を知りました。

 行と列わからないんです>< でもこれで忘れない!

*1:不都合が起こるパターンもあるのだろうか?