Гобелен в выберите компонент является очень гибким и эта гибкость приходит за небольшую плату. Нужно передать SelectModel и ValueEncoder в качестве параметров компоненту Select. Это кажется слишком большой работой для простых случаев использования. С помощью преобразований классов мы всегда можем найти ярлыки. В этой статье я расскажу о некоторых полезных методах, упомянутых в вики, чтобы упростить использование компонента Select.
использование
Давайте посмотрим, как компонент Select будет работать, используя эту новую технику. Мы аннотируем список элементов / опций с помощью @InjectSelectSupport
public class MyPage {
//Items for Select
@InjectSelectSupport(type=User.class, label="${user} (${address})", index="id")
private List<User> users;
//Value for Select
@Property
private User user;
void onActivate(){
users = load_values_from_database();
}
}
и в шаблоне мы устанавливаем параметры модели и датчика в имя поля опций с добавлением «Поддержка».
<form t:type='form'>
...
<select t:type='select' t:value='user' t:model='usersSupport' t:encoder='usersSupport'>
</select>
...
</form>
шляпа это
Как это работает
Сначала мы создаем универсальные SelectModel и ValueEncoder.
public class SelectSupport<T> extends AbstractSelectModel implements ValueEncoder<T> { private final List<T> items; private Map<String, PropertyAdapter> adapterMap = new HashMap<String, PropertyAdapter>(); private String label; private PropertyAdapter indexPropertyAdapter; private TypeCoercer typeCoercer; private final static Pattern PROPERTY_PATTERN = Pattern.compile("\\$\\{([\\w.$]+)\\}"); public SelectSupport(final List<T> items, final String label, String indexProperty, final Class<?> valueType, final PropertyAccess access, TypeCoercer typeCoercer) { this.items = items; this.label = label; indexPropertyAdapter = access.getAdapter(valueType).getPropertyAdapter(indexProperty); Matcher matcher = PROPERTY_PATTERN.matcher(label); this.typeCoercer = typeCoercer; while (matcher.find()) { adapterMap.put(matcher.group(0), access.getAdapter(valueType).getPropertyAdapter(matcher.group(1))); } } public List<OptionGroupModel> getOptionGroups() { return null; } public List<OptionModel> getOptions() { final List<OptionModel> options = new ArrayList<OptionModel>(); if (items != null) { for(T item: items){ options.add(new OptionModelImpl(toLabel(item), item)); } } return options; } private String toLabel(Object object) { String label = this.label; for (String key : adapterMap.keySet()) { label = label.replace(key, adapterMap.get(key).get(object).toString()); } return label; } /** * {@inheritDoc} */ public String toClient(T value) { return typeCoercer.coerce(getIndex(value), String.class); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public T toValue(String clientValue) { for(T value: items){ if(getIndex(value).equals(typeCoercer.coerce(clientValue, indexPropertyAdapter.getType()))){ return value; } } return null; } /** * Gets the index value for a particular item * * @param value */ private Object getIndex(Object value){ Object fieldValue = indexPropertyAdapter.get(value); if(fieldValue == null){ throw new RuntimeException("Index property cannot be null"); } return fieldValue; } }
Он принимает список элементов, выражение метки и имя свойства индекса в качестве параметров и реализует интерфейс SelectModel и ValueEncoder. Выражение метки может содержать любую комбинацию имен текста и свойств с именами свойств, заключенными в $ {}. Он использует службу PropertyAccess для получения PropertyAdapter для каждого свойства, используемого в выражении метки, а затем использует PropertyAdapter для чтения значений этих свойств. Служба TypeCoercer используется для преобразования между уникальным значением индекса элемента и значением String, переданным в качестве атрибута значения в тег <option>.
Теперь мы можем использовать SelectSupport в нашем компоненте / странице как
@Inject
private PropertyAccess propertyAccess;
@Inject
private TypeCoercer typeCoercer;
private SelectSupport _selectSupport;
public SelectSupport getUsersSupport(){
if(_selectSupport == null){
_selectSupport = new SelectSupport(users,
annotation.label(), annotation.index(), annotation.type(),
propertyAccess, typeCoercer);
}
return _selectSupport;
или мы можем создать преобразование класса. Для этого нам потребуется аннотация
public @interface InjectSelectSupport {
/**
* Expression to be used as label for select model. This takes an
* expression as a parameter. The expression can be any property
* value with <code>${}</code>.
*/
String label();
/**
* Property to be used as index. It's value must be unique for each
* item
*/
String index();
/**
* This suffix is appended to the generated method name.
* Default is <code>Support</code>
*/
String methodSuffix() default "Support";
/**
* Item type.
*/
Class<?> type();
}
Преобразование класса ищет любое поле, помеченное @InjectSelectSupport, и генерирует / вводит метод получения для SelectSupport в компоненте / странице, который затем можно использовать для установки параметров модели и кодировщика компонента Select.
public class InjectSelectSupportWorker implements ComponentClassTransformWorker {
private PropertyAccess propertyAccess;
private TypeCoercer typeCoercer;
public InjectSelectSupportWorker(PropertyAccess propertyAccess, TypeCoercer typeCoercer) {
this.propertyAccess = propertyAccess;
this.typeCoercer = typeCoercer;
}
public void transform(ClassTransformation transform, MutableComponentModel model) {
for (final TransformField field : transform.matchFieldsWithAnnotation(InjectSelectSupport.class)) {
final InjectSelectSupport annotation = field.getAnnotation(InjectSelectSupport.class);
String selectSupportPropertyName = field.getName() + annotation.methodSuffix();
String methodName = "get" + InternalUtils.capitalize(selectSupportPropertyName);
TransformMethodSignature sig = new TransformMethodSignature(Modifier.PUBLIC,
SelectSupport.class.getName(), methodName, null, null);
final TransformMethod method = transform.getOrCreateMethod(sig);
// Add a field to cache the result
final TransformField selectSupportField = transform.createField(Modifier.PRIVATE,
SelectSupport.class.getName(), "_$" + selectSupportPropertyName);
final FieldAccess selectSupportFieldAccess = selectSupportField.getAccess();
final FieldAccess access = field.getAccess();
method.addAdvice(new ComponentMethodAdvice() {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void advise(ComponentMethodInvocation invocation) {
Object instance = invocation.getInstance();
if (selectSupportFieldAccess.read(instance) == null) {
selectSupportFieldAccess.write(
instance,
new SelectSupport((List) access.read(invocation.getInstance()),
annotation.label(), annotation.index(), annotation.type(),
propertyAccess, typeCoercer));
}
invocation.overrideResult(selectSupportFieldAccess.read(instance));
}
});
}
}
Наконец, преобразование класса вносится в цепочку ComponentClassTransformWorker.
@Contribute(ComponentClassTransformWorker.class)
public static void provideWorkers(OrderedConfiguration<ComponentClassTransformWorker> workers) {
workers.addInstance("InjectSelectSupport", InjectSelectSupportWorker.class);
}
С http://tawus.wordpress.com/2011/06/04/tapestry-magic-14-easy-selection/