IComponentResolverとpropertiesファイルのtips
以前IComponentResolverについて書いたんだけど *1 *2 、この必須チェックが予想以上に便利で使いやすい。
それで、リスト表示でも使いたくなったので少しだけ汎用化した。
EntityComponentResolver.java
import java.lang.reflect.Field; import javax.persistence.Column; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.basic.MultiLineLabel; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.markup.html.form.PasswordTextField; import org.apache.wicket.markup.html.form.TextArea; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.resolver.IComponentResolver; @SuppressWarnings("serial") public class EntityComponentResolver<T> implements IComponentResolver { public interface Parent { public void setAutoAdd(Component component); } private static final String MULTI_LINE = "attr:multiline"; private final Class<?> clazz; // clazzまたはentityのどちらかを使う private final T entity; private final Parent parent; public EntityComponentResolver(Parent parent, Class<?> clazz) { this.clazz = clazz; this.entity = null; this.parent = parent; } public EntityComponentResolver(Parent parent, T entity) { this.clazz = null; this.entity = entity; this.parent = parent; } @Override public Component resolve(MarkupContainer container, MarkupStream stream, ComponentTag tag) { if (tag.isAutoComponentTag()) return null; // wicketが自動で追加するタグ String tagName = tag.getName().toLowerCase(); if (tagName.equals("input")){ if (tag.getAttribute("type").equals("text")) return addFormComponent(new TextField<Void>(tag.getId()), tag, container, stream); else if (tag.getAttribute("type").equals("password")) return addFormComponent(new PasswordTextField(tag.getId()).setResetPassword(false), tag, container, stream); throw new RuntimeException("Unknown input tag."); }else if (tagName.equals("textarea")){ return addFormComponent(new TextArea<Void>(tag.getId()), tag, container, stream); }else{ String multiline = tag.getAttribute(MULTI_LINE); Component component; if (multiline != null && multiline.equals("true")) component = new MultiLineLabel(tag.getId()); else component = new Label(tag.getId()); return component; } } /** * Entityのアノテーションを見て制約を設定する。 * @param component * @param tag * @return */ private Component addFormComponent(FormComponent<?> component, ComponentTag tag, MarkupContainer container, MarkupStream markupStream){ try { Field f; if (clazz != null) f = clazz.getField(tag.getId()); else f = entity.getClass().getField(tag.getId()); Column col = f.getAnnotation(Column.class); if (col != null){ if (!col.nullable()) component.setRequired(true); } } catch (SecurityException e) { } catch (NoSuchFieldException e) { } addForRender(component, container, markupStream); return component; } private void addForRender(Component component, MarkupContainer container, MarkupStream markupStream){ if (markupStream != null){ component.setMarkup(markupStream.getMarkupFragment()); } parent.setAutoAdd(component); } }
EntityComponent.java
import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.resolver.IComponentResolver; import org.apache.wicket.model.CompoundPropertyModel; /** * エンティティクラスから自動でコンポーネントを生成してaddするコンテナ。 * * <pre>有効な属性 * - attr:multiline="true" MultiLineLabelを生成する。 * </pre> * @author nishimura * @param <T> Entityクラス */ @SuppressWarnings("serial") public class EntityComponent<T> extends WebMarkupContainer implements IComponentResolver, EntityComponentResolver.Parent { private final EntityComponentResolver<T> entityComponentResolver; public EntityComponent(String id, T obj) { super(id, new CompoundPropertyModel<T>(obj)); entityComponentResolver = new EntityComponentResolver<T>(this, obj); } @Override public Component resolve(MarkupContainer container, MarkupStream stream, ComponentTag tag) { return entityComponentResolver.resolve(container, stream, tag); } @Override public void setAutoAdd(Component component) { component.setParent(this); setAuto(true); add(component); setAuto(false); } }
EntityDataView.java
import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.MarkupStream; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.markup.repeater.data.DataView; import org.apache.wicket.markup.repeater.data.IDataProvider; import org.apache.wicket.markup.resolver.IComponentResolver; import org.apache.wicket.model.CompoundPropertyModel; @SuppressWarnings("serial") public class EntityDataView<T> extends DataView<T> implements IComponentResolver, EntityComponentResolver.Parent { private final EntityComponentResolver<T> entityComponentResolver; public EntityDataView(String id, IDataProvider<T> dataProvider, Class<T> clazz) { super(id, dataProvider); entityComponentResolver = new EntityComponentResolver<T>(this, clazz); } public EntityDataView(String id, IDataProvider<T> dataProvider, int itemsPerPage, Class<T> clazz) { super(id, dataProvider, itemsPerPage); entityComponentResolver = new EntityComponentResolver<T>(this, clazz); } @Override public Component resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag) { return entityComponentResolver.resolve(container, markupStream, tag); } @Override protected void populateItem(Item<T> item) { item.setModel(new CompoundPropertyModel<T>(item.getModelObject())); } @Override public void setAutoAdd(Component component) { component.setParent(this); setAuto(true); add(component); setAuto(false); } }
これでリスト表示にも使えるようになった。
あと共通のprpoertiesファイルにエンティティのフィールド名を書くとより使いやすくなる。
MyApplication.utf8.properties
entity.goods.name = 商品名 entity.goods.code = 商品番号
ListPage.html
<table> <tr> <th><wicket:message key="entity.goods.name">name</wicket:message></th> <th><wicket:message key="entity.goods.code">code</wicket:message></th> </tr> <tr wicket:id="goods"> <td wicket:id="name">name</td> <td wicket:id="code">code</td> </tr> </table>
EditPage.html
<form wicket:id="entity"> <table wicket:id="goods"> <tr> <th><wicket:message key="name">name</wicket:message></th> <td><input type="text" wicket:id="name"></td> </tr> <tr> <th><wicket:message key="code">code</wicket:message></th> <td><input type="text" wicket:id="code"></td> </tr> </table> </form>
こんな感じで、表示用のラベルにも使えるしフォームのエラーメッセージにも利用できるので手間がだいぶ減った。