Статьи

Гобелен 5.3+: новые возможности

Tapestry 5.3 готова к бета-версии, и в ней есть много интересных функций. Вы можете прочитать о них здесь . Цель этого и следующего за ним поста состоит в том, чтобы предоставить несколько примеров для начала работы.

Периодический исполнитель

Гобелен теперь имеет PeriodicExecutor. Простая реализация планировщика, но, конечно, не замена Quartz (по крайней мере, пока). Будучи простой реализацией, она очень проста в использовании.

Давайте создадим простую работу, которая увеличивает счетчик. Для этого мы сначала создадим простой счетчик сервиса.

//Interface
public interface CounterService
{

void reset();

int getValue();

void increment();

}

//Implementation
public class CounterServiceImpl implements CounterService
{

private AtomicInteger counter = new AtomicInteger();

public void reset()
{
counter.set(0);
}

public int getValue()
{
return counter.get();
}

public void increment()
{
counter.addAndGet(1);
}

}

Работа должна быть любой Runnable реализацией

public class SimpleJob implements Runnable
{

private CounterService counterService;

public SimpleJob(CounterService counterService)
{
this.counterService = counterService;
}

public void run()
{
counterService.increment();
}
}

Теперь давайте создадим простую страницу для мониторинга этой работы

public class SimpleJobDemo
{
@Inject
private PeriodicExecutor executor;

@Persist
@Property
private PeriodicJob periodicJob;

@Inject
@Property
private CounterService counterService;

@InjectComponent
private Zone zone;

void onActivate()
{
if(periodicJob == null)
{
start();
}
}

Object onZoneRefresh()
{
return periodicJob.isCanceled() ? null : zone.getBody();
}

void onCancel()
{
periodicJob.cancel();
}

void onRestart()
{
start();
}

private void start()
{
counterService.reset();
SimpleJob job = new SimpleJob(counterService);
periodicJob = executor.addJob(ScheduleUtils.secondlySchedule(1),
"My Counter Job", job);
}

}
<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd' xmlns:p='tapestry:parameter'>

<head>
<title>Simple Job Demo</title>
</head>

<body>
<h1>Simple Job Demo</h1>

<div t:type='zone' t:id='zone' t:mixins='zoneRefresh' t:period='5'>
<strong>Counter : </strong>
${counterService.value}
<br />

<strong>Is Executing ? </strong>
${periodicJob.executing}
<br />

<strong>Is Cancelled ? </strong>
${periodicJob.canceled}
<br />

<t:if test='periodicJob.canceled'>
<a href='#' t:type='eventlink' t:event='restart'>Start Job</a>
<p:else>
<a href='#' t:type='eventlink' t:event='cancel'>Cancel Job</a>
</p:else>
</t:if>
<t:unless test='periodicJob.canceled'>

</t:unless>
</div>

</body>
</html>

Я создал вспомогательный класс для создания расписаний

public class ScheduleUtils
{
public static Schedule hourlySchedule(int hours)
{
return new IntervalSchedule(hours * 60 * 60 * 1000L);
}

public static Schedule minutelySchedule(int minutes)
{
return new IntervalSchedule(minutes * 60 * 1000L);
}

public static Schedule secondlySchedule(int seconds)
{
return new IntervalSchedule(seconds * 1000L);
}
}

Таким образом, все, что вам нужно сделать, это получить службу PeriodicExecutor и зарегистрировать вашу работу (которая должна быть Runnable). Он возвращает вам экземпляр PeriodicJob, который можно использовать для отслеживания или отмены задания.

Компонент контрольного списка

Контрольный список похож на компонент Checkbox-group, который можно найти во многих библиотеках / инфраструктурах. Он отображает несколько значений в виде флажков и позволяет выбрать несколько значений, установив соответствующие флажки.

Чтобы использовать его, вам нужны SelectModel и ValueEncoder. Я начну с доменного объекта

public class Fruit
{
private String name;

public Fruit(String name)
{
this.setName(name);
}

public void setName(String name)
{
this.name = name;
}

public String getName()
{
return name;
}

@Override
public int hashCode(){
return name.hashCode();
}

@Override
public boolean equals(Object value){
if(value == this){
return true;
}

if(value == null || !(value instanceof Fruit))
{
return false;
}

Fruit other = (Fruit)value;

return getName().equals(other.getName());
}

@Override
public String toString(){
return name;
}

}

(Постился целый месяц, поэтому не могу думать ни о каком другом объекте домена. !!). Модель выбора

public class FruitSelectModel extends AbstractSelectModel
{

private Iterable<Fruit> fruits;

public FruitSelectModel(Iterable<Fruit> fruits)
{
this.fruits = fruits;
}

public List<OptionGroupModel> getOptionGroups()
{
return null;
}

public List<OptionModel> getOptions()
{
List<OptionModel> optionModels = new ArrayList<OptionModel>();

for(Fruit fruit: fruits){
optionModels.add(new OptionModelImpl(fruit.getName(), fruit));
}

return optionModels;
}

}

и ValueEncoder

public class FruitValueEncoder implements ValueEncoder<Fruit>
{
private Iterable<Fruit> fruits;

public FruitValueEncoder(Iterable<Fruit> fruits)
{
this.fruits = fruits;
}

public String toClient(Fruit fruit)
{
return fruit.getName();
}

public Fruit toValue(String fruitName)
{
for(Fruit fruit: fruits)
{
if(fruit.getName().equals(fruitName))
{
return fruit;
}
}

throw new RuntimeException("Invalid fruit name returned from client: " + fruitName);
}

}

Теперь мы готовы использовать контрольный список на странице.

public class ChecklistDemo
{
@SuppressWarnings("unused")
@Property
@Persist(PersistenceConstants.FLASH)
private List<Fruit> selectedFruits;

private List<Fruit> fruits;

@SuppressWarnings("unused")
@Property(write = false)
private SelectModel fruitModel;

@SuppressWarnings("unused")
@Property(write = false)
private FruitValueEncoder fruitEncoder;

void onActivate()
{
addFruits();
fruitModel = new FruitSelectModel(fruits);
fruitEncoder = new FruitValueEncoder(fruits);
}

private void addFruits()
{
fruits = new ArrayList<Fruit>();

for(String fruitName : new String[] { "Apple", "Banana", "Mango", "Melon" })
{
fruits.add(new Fruit(fruitName));
}
}

}

 

<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>
<head>
<title>Checklist Demo</title>
</head>

<body>
<h3>Check List Demo</h3>

<t:if test='selectedFruits'>
<strong>You have selected ${selectedFruits}</strong>
</t:if>

<form t:type='form'>
<label t:type='label' t:for='checklist'/><br/>

<div t:type='checklist' t:id='checklist'
t:selected='selectedFruits' t:encoder='fruitEncoder'
t:model='fruitModel'></div>

<input type='submit' name='submit' value='Submit'/>

</form>

</body>

</html>

дерево

Это одна из самых интересных функций в Tapestry 5.3+. Самое приятное то, насколько легко им пользоваться. Обычно компонент дерева — это тот, который обсуждается в конце книги с графическим интерфейсом в разделе «Расширенные компоненты», но этот компонент можно просто вставить в раздел «Начало работы».

Итак, давайте создадим файловый браузер. Нам нужно предоставить TreeModelAdapter, чтобы сообщить дереву, чего ожидать от определенного узла.

public class FileAdapter implements TreeModelAdapter<File>
{

public boolean isLeaf(File file)
{
return !file.isDirectory();
}

public boolean hasChildren(File file)
{
return file.isDirectory();
}

public List<File> getChildren(File file)
{
return Arrays.asList(file.listFiles());
}

public String getLabel(File file)
{
return file.getName();
}

}

Вы также должны предоставить ValueEncoder, который будет добавлен в строку. Так что вот собственный браузер файлов

public class FileBrowser
{
private File directory;

void onActivate()
{
directory = new File(".");
}

public TreeModel<File> getFileModel()
{
ValueEncoder<File> encoder = new ValueEncoder<File>()
{

public String toClient(File file)
{
return file.getAbsolutePath();
}

public File toValue(String name)
{
return new File(name);
}
};

return new DefaultTreeModel<File>(encoder, new FileAdapter(),
Arrays.asList(getRootDirectory().listFiles()));

}

public File getRootDirectory()
{
return directory;
}

}

Мы передаем TreeModel компоненту дерева. Мы используем DefaultTreeModel, который требует TreeModelAdapter и ValueEncoder вместе с корневыми элементами. Да, это так !! Придумаю еще один пост, чтобы показать больше возможностей.

 

От http://tawus.wordpress.com/2011/08/24/tapestry53/