
Fullstack веб-приложение за 30 минут? Звучит интересно. Учитывая что при этом без использования css и js можно получить годный дизайн и нормальную адаптивную верстку!
Vaadin 8
Вообще с самим Vaadin я уже работал, он отлично подходит для написания админок, корпоративных не сильно нагруженных АРМов, позволяя создать фронт на native-java.
Относительно недавно вышедшая новая 8 версия внесла много крутых изменений (21 глобальное, если точно), писать кода можно сильно меньше, скорость работы прилично выросла.
Поддерживает хорошую интеграцию с spring-boot, что позволяет избежать настроек томката и реально за 5 минут получить работающий скелет.
TODO application
Предлагаю рассмотреть создание маленького веб-приложения — TODO (списка дел).
Для начала очень удобно и быстро собрать скелет помогает Spring Initializr

На скриншоте я показал нужные нам компоненты для нашего проекта: Web, Vaadin, JPA и Н2 (в качестве БД).
Нажимаем Generate и скачиваем готовый maven проект с нужными нам зависимостями. Остается только импортировать его в любимую IDE (у меня это IDEA).
После начинается магия :)
Главное UI
Создадим класс TodoUI
на который повесим аннотацию @SpringUI
, этим дадим знать spring`у что это наш главный UI приложения.
@SpringUI
public class TodoUI extends UI {
private VerticalLayout root;
@Autowired
private TodoListLayout todoLayout;
@Override
protected void init(VaadinRequest vaadinRequest) {
setupLayout();
addHeader();
addForm();
addTodoList();
addDeleteButton();
}
}
В перегруженный метод init()
добавим методы инициализации наших нашего интерфейса, root
— это главный layout, он может быть только один и задается методом setContent()
Теперь по порядку:
Создадим root, зададим выравнивание
private void setupLayout() {
root = new VerticalLayout();
root.setDefaultComponentAlignment(Alignment.MIDDLE_CENTER);
setContent(root);
}
Добавим хидер. Сразу зададим стиль
private void addHeader() {
Label header = new Label("TODO");
header.addStyleName(ValoTheme.LABEL_H1);
root.addComponent(header);
}
Создадим форму
private void addForm() {
HorizontalLayout formLayout = new HorizontalLayout();
formLayout.setWidth("80%"); // Поле для ввода, и кнопочку добавления
TextField task = new TextField();
Button add = new Button("");
add.addStyleName(ValoTheme.BUTTON_PRIMARY);
add.setIcon(VaadinIcons.PLUS); // Поле ввода займет всю ширину за минусом минимальной для кнопки
formLayout.addComponentsAndExpand(task);
formLayout.addComponents(add); // Лисенер для кнопки Добавить
add.addClickListener(click -> {
todoLayout.add(new Todo(task.getValue()));
task.clear();
task.focus();
});
task.focus(); // Кнопка плюс будет работать по нажатию ENTER
add.setClickShortcut(ShortcutAction.KeyCode.ENTER);
root.addComponent(formLayout);
}
Добавим список самих задач. Он подключается через @Autowired
спрингом
private void addTodoList() {
todoLayout.setWidth("80%");
root.addComponent(todoLayout);
}
Кнопку удаления выполненных задач. С логикой удаления и проверки на «ничего не выбрано»
private void addDeleteButton() {
root.addComponent(new Button("Delete completed", click -> {
if (todoLayout.countCompleted() == 0) {
Notification.show("No completed task", Notification.Type.HUMANIZED_MESSAGE);
}
todoLayout.deleteCompleted();
}));
}
Entry & JPA
Сама сущность весьма проста. Опускаю конструкторы и set/get методы
@Entity
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String text;
private boolean done;
}
И настоящая магия Jpa и Spring-data — репозиторий!
public interface TodoRepository extends JpaRepository<Todo, Long> {
@Transactional
void deleteByDone(boolean done);
int countByDone(boolean done);
}
Да, достаточно просто создать интерфейс, написать метод и все дальше произойдет само. По имени метода будет сгенерирован запрос к базе. Черт, это реально круто! Никакого SQL :)
Layout для отдельной задачи
В простейшим случае нам нужно иметь CheckBox
+ TextField
. Добавим эти 2 объекта на layout, зададим стиль.
public class TodoItemLayout extends HorizontalLayout {
private final TextField text;
private final CheckBox done;
public TodoItemLayout(Todo todo, TodoChangeListener changeListener) {
setWidth("100%");
setDefaultComponentAlignment(Alignment.MIDDLE_LEFT);
text = new TextField();
done = new CheckBox();
text.addStyleName(ValoTheme.TEXTFIELD_BORDERLESS);
text.setValueChangeMode(ValueChangeMode.BLUR);
addComponents(done);
addComponentsAndExpand(text);
}
}
Самое интересное тут — Vaadin Binder
. Он позволяет связать jpa сущность с UI. Для этого нам нужно соответствие полей entity и layout класса.
// Создадим Binder
Binder<Todo> binder = new Binder<>(Todo.class);
binder.bindInstanceFields(this);
binder.setBean(todo);
// Добавим лисенер для обновления информации с чекбокса
binder.addValueChangeListener(event -> changeListener.todoChanged(todo));
Список всех задач
И последний класс — список всех задач. Он будет содержать все TodoItemLayout
и работать с TodoRepository
.
Тут важно не забыть обновлять компонент при изменениях и инициализации
@SpringComponent
public class TodoListLayout extends VerticalLayout implements TodoChangeListener {
@Autowired
TodoRepository repository;
@PostConstruct
void init() {
update();
}
private void update() {
setTodos(repository.findAll());
}
private void setTodos(List<Todo> all) {
removeAllComponents();
all.forEach(todo -> addComponent(new TodoItemLayout(todo, this)));
}
public void add(Todo todo) {
repository.save(todo);
update();
}
public int countCompleted() {
return repository.countByDone(true);
}
public void deleteCompleted() {
repository.deleteByDone(true);
update();
}
@Override
public void todoChanged(Todo todo) {
add(todo);
}
}
Не забудем про интерфейс
public interface TodoChangeListener {
void todoChanged(Todo todo);
}
Наполнение БД
В resources создадим файл data.sql
, в котором напишем команды для создания и наполнения таблицы
CREATE TABLE IF NOT EXISTS Todo(
id IDENTITY PRIMARY KEY,
done BOOLEAN,
TEXT VARCHAR
);
DELETE FROM Todo;
INSERT INTO Todo VALUES(1, TRUE, 'Do something');
INSERT INTO Todo VALUES(2, FALSE, 'Do something else');
INSERT INTO Todo VALUES(3, TRUE, 'Test application');
Запуск
Все готово. Собираем и запускаем проект:
mvn package spring-boot:run
После можно проверить работу по адресу localhost:8080, если порт уже занят, открываем application.properties
и прописываем
server.port=8081
Готово!
На подлесок идеи того, что еще можно прикрутить:
- Заменить удаление на «сохранение в выполненные»
- Добавить приоритет
- Настроить персистентность БД
- Добавить поддержку пользователей
- Сделать конкурента Wunderlist :)
Github: https://github.com/sboychenko/vaadin-todo
Тимур
23 марта 2017 — 15:42
Скажите, уважаемый!
Подскажите, а как можно подцепить сторонний REST API, который реализован например на Ratcpack?
Сергей Бойченко
27 марта 2017 — 11:45
Например, так: https://vaadin.com/blog/-/blogs/consuming-rest-services-from-java-applications. Это не совсем сторонний rest api, но иных вариантов я не нашел
Тимур
26 марта 2017 — 20:04
У вас шаринг plus.google.com не работает
Сергей Бойченко
27 марта 2017 — 22:53
Хм. Проверил сейчас, вроде сработал…
Anton Romanov
16 февраля 2018 — 17:24
Сергей, огромное спасибо за материал. Пытаюсь сейчас переделать Ваш код. Мне нужно, чтобы страничка выводила таблицу, в которую какой-то метод какого-то класса…. не важной какой, пусть это будет просто набор заданных строк… в общем чтобы этот метод выкидывал в табличку данные и чтобы я мог этот метод вызвать по нажатию кнопки Update на странице. То есть проще говоря мне нужна табличка с кнопкой ее обновления. Как это реализовать? может вам попадались примеры кода?
Сергей Бойченко
16 февраля 2018 — 18:25
У ваадина есть sampler, где много примеров с кодом.
https://demo.vaadin.com/sampler/#ui/grids-and-trees/grid/features — вот таблица
Так же есть хорошая документация на сайте https://vaadin.com/docs/v8/framework/components/components-grid.html вот про таблицы.
По сути, нужно просто добавить таблицу и кнопку в UI, на кнопку повесить listener, который будет обновлять данные. Если нужно автоматическая перерисовка таблицы по событию в бекенде смотрите в сторону vaadin push.
Anton Romanov
16 февраля 2018 — 18:48
Сергей, огромное Вам спасибо. В принципе, у меня даже получилось полностью сделать, что я хочу. Табличка уже висит. Код забрал у них:
private void addTable() {
List people = Arrays.asList(
new Person(«Nic», 1543),
new Person(«Nic», 1544));
VerticalLayout tableLayout = new VerticalLayout();
Grid grid = new Grid);
grid.setItems(people);
grid.addColumn(Person::getName).setCaption(«Name»);
tableLayout.addComponent(grid);
root.addComponent(tableLayout);
}
Что не пойму: сейчас данные в табличку по сути попадают при инициализации внутри private void addTable. А как подвесить этот код в листенер? На сколько я могу предположить — надо городить отдельный метод у кого-то, который я потом подпихну туда? (заранее сорри за глупые вопросы)
Сергей Бойченко
16 февраля 2018 — 19:11
Там по сути нужно вызвать метод grid.setItems(); это обновит таблицу
Anton Romanov
22 февраля 2018 — 17:16
В общем, вот что получилось — https://github.com/RESTfulSPBLessons/Vaadin-Table/tree/xml-parser-with-vaadin-FINAL.
Мне нужно было сделать парсер XML RSS-фидов с использованием Vaadin, SpringBoot, Jaxb и чтобы там была кнопка обновления.
Сергей Бойченко
23 февраля 2018 — 23:36
Класс!