Гобелен в выберите компонент является очень гибким и эта гибкость приходит за небольшую плату. Нужно передать 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/