Vuecket — веб-фреймворк, интегрирующий VueJS с клиентской стороны и Apache Wicket с серверной. Он берет все самое лучшее от обоих и позволяет разрабатывать full-stack приложения еще быстрее и проще. Конечно, это всё громкие слова, ведь Vuecket’у на данный момент (Август 2020) меньше месяца, и он не прошел ещё крещение «огнем и кровью» продакшн серверов. Но он уже включил в себя всё самое лучшее из наработанного нами при разработке нашего ключевого Open Source продукта Orienteer(платформа для быстрой разработки бизнес приложений). И именно из-за молодого возраста Vuecket’у нужна ваша помощь: поделитесь, пожалуйста, тем, что вам понравилось, что не очень, где нужна доработка и т.д.
Основные принципы, которыми мы руководствуемся при построении Vuecket’а:
- Быть декларативным — не императивным. Vuecket не диктует какие-то специальные требования к коду. Он может быть приложен достаточно быстро и легко к уже существующим Vue.JS или Apache Wicket проектам.
- Следовать принципу Парето. Vuecket должен обеспечивать 80% нужного функционала из коробки, но для оставшихся 20% должны быть хорошие и удобные точки расширения.
Легко заметить, что эти принципы также применимы и к Vue.JS, и к Apache Wicket.
Итак, как именно мы будем знакомиться с Vuecket? Предлагаю сделать чат/гостевую доску с поддержкой Markdown. Не буду сильно томить: законченное приложение здесь, а код здесь.
Создаем проект
Сгенерируем наш проект через `mvn archetype:generate`. Для этого можете использовать, например, следующую команду:
mvn archetype:generate -DarchetypeGroupId=org.apache.wicket -DarchetypeArtifactId=wicket-archetype-quickstart -DarchetypeVersion=8.9.0 -DgroupId=com.mycompany -DartifactId=mychat -DarchetypeRepository=https://repository.apache.org/ -DinteractiveMode=false
Vuecket пока не обзавелся собственным темплейтом проекта Maven. Возможно, в будущем, мы добавим и это. Теперь подключим сам Vuecket. Добавьте следующую зависимость в `pom.xml` проекта:
<dependency> <groupId>org.orienteer.vuecket</groupId> <artifactId>vuecket</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
Вывод текста в Markdown
Проект Wicket по умолчанию уже содержит в себе страницу-приветствие Wicket. Давайте в нее добавим немного кода, чтобы убедиться, что Vuecket уже работает. Например, выведем Hello World, но в Markdown, и так, чтобы сам текст задавался на стороне сервера в Apache Wicket компоненте. Для отрисовки Markdown будем использовать библиотеку vue-markdown.
В HomePage.html вместо приветствия Wicket добавляем:
<div wicket:id="app"> <vue-markdown wicket:id="markdown">This will be replaced</vue-markdown> </div>
А в HomePage.java следующий код:
public HomePage(final PageParameters parameters) { super(parameters); add(new VueComponent<String>("app") .add(new VueMarkdown("markdown", "# Hello World from Vuecket"))); }
Но где класс VueMarkdown? А его мы определим следующим образом:
@VueNpm(packageName = "vue-markdown", path = "dist/vue-markdown.js", enablement = "Vue.use(VueMarkdown)") public class VueMarkdown extends Label { public VueMarkdown(String id) { super(id); } public VueMarkdown(String id, Serializable label) { super(id, label); } }
Обратите внимание на аннотацию @VueNpm. Она необходима, чтобы включить Vuecket на данном Wicket компоненте, который подгрузит все необходимое из NPM, чтобы помочь браузеру отобразить уже Vue компонент для Markdown правильно.
Итак, что здесь произошло, и почему это работает?
- Мы разметили страницу, добавив в неё 2 Vue компонента: для приложения и для вывода Markdown’а
- Мы связали Vue компоненты с Wicket компонентами на стороне сервера (в HomePage.java)
- Мы указали Vuecket’у, какая именно Vue.JS библиотека нужна для отрисовки ‘vue-markdown’
- А дальше всё просто: Wicket при отрисовке страницы браузеру использовал строчку "# Hello World from Vuecket", которую мы задали при добавлении Wicket компонента, а Vuecket помог браузеру загрузить нужные VueJS библиотеки, запустить VueJS приложение и отрисовать приветствие уже как отрендеренный Markdown
Ввод сообщения и его предпросмотр
На этом шаге мы усложним наше приложение: сделаем ввод сообщения и его предпросмотр.
Добавим textarea в HomePage.html для ввода сообщения, а так же привяжем это поле и vue-markdown к VueJS переменной «text».
<div wicket:id="app"> <textarea v-model="text" style="width:100%" rows="5"></textarea> <vue-markdown wicket:id="markdown" :source="text">Will be replaced</vue-markdown> </div>
Мы уже используем переменную «text», но теперь надо её добавить в data Vue компонент. Здесь несколько путей сделать это в Vuecket, но давайте пойдем самым долгим:
- Создадим свой VueComponent для Vue приложения
- Проассоциируем его со своим *.vue файлом
- Пропишем в *.vue файле логику: на данный момент, просто поле «text»
Вот примерно какие изменения мы сделаем:
//HomePage.java: public HomePage(final PageParameters parameters) { super(parameters); add(new ChatApp("app") .add(new VueMarkdown("markdown"))); } //ChatApp.java: @VueFile("ChatApp.vue") public class ChatApp extends VueComponent<Void> { public ChatApp(String id) { super(id); } }
Ну и сам ChatApp.vue:
<script> module.exports = { data: function() { return { text : "" } } } </script>
В этой главе мы научились: создавать всем привычные *.vue файлы и ассоциировать их с компонентами Apache Wicket
Отображение списка сообщений и добавление нового
В этой главе не будет ничего Vuecket или Wicket специфичного: чистое сияние VueJS.
Если декомпозировать задачу, то нам надо будет сделать следующее:
- Добавить в наше Vue приложение поле-список для сохранения сообщений
- Добавить метод для добавления нового сообщения в список
- Отобразить список сообщений и не забыть про markdown
Для начала изменим наш ChatApp.vue и добавим нужную логику: новое поле `messages` со списком сообщений и метод `addMessage` для добавления нового сообщения. И не забудем, что при добавлении сообщения в список хорошо бы очистить исходное поле ввода. Для сообщений будем хранить не только текст, но и дату добавления/отсылки. В будущем можно будет рассширить дополнительными полями, например, кто послал данное сообщение, приоритет, нужная подсветка и т.п.
<script> module.exports = { data: function() { return { text : "", messages: [] } }, methods: { addMessage : function() { this.messages.push({ message: this.text, date: new Date() }); this.text = ""; } } } </script>
Так же поменяем HomePage.html, добавим отображение списка сообщений и добавим вызов нашего метода addMessage при нажатии на Ctrl-Enter.
<div wicket:id="app"> <div v-for="(m, index) in messages"> <h5>{{ index }}: {{ m.date }}</h5> <vue-markdown :source="m.message"></vue-markdown> </div> <textarea v-model="text" style="width:100%" rows="5" @keyup.ctrl.enter="addMessage" placeholder="Enter message and Ctrl-Enter to add the message"> </textarea> <vue-markdown wicket:id="markdown" :source="text">Will be replaced</vue-markdown> </div>
В этой главе мы лишь средствами VueJS научили приложение добавлять сообщение в список и отображать последний.
Включаем коллаборацию
Если до этого содержание нашей гостевой книги было для каждого посетителя страницы уникальным, то в этой главе мы включим связь с сервером и позволим синхронизацию со всеми посетителями. Для этого нам понадобятся Vuecket Data Fibers — решение, которое позволяет синхронизировать объекты на стороне браузера с объектами на стороне сервера. И что самое интересное, нам ничего не понадобится делать для этого на стороне клиентского кода! Звучит круто? Поехали кодить! Хотя… Тут всего две новые строчки в нашем компоненте ChatApp.java:
private static final IModel<List<JsonNode>> MESSAGES = Model.ofList(new ArrayList<JsonNode>()); public ChatApp(String id) { super(id); addDataFiber("messages", MESSAGES, true, true, true); }
Что тут произошло:
- Мы создали модель MESSAGES, которая доступна всем, так как создана как static final.
- Добавили data-fiber, который связывает объект messages на сторое клиента и объект внутри модели MESSAGES на стороне сервера.
- Указали, что data-fiber нам нужен для 3 вещей: load, observe, refresh. Load — для первоначальной подгрузка данных, Observe — для синхронизации с сервером изменений на стороне клиента, Refresh — для периодической подгрузки данных со стороны сервера.
Серверные методы
В предыдущей главе я чуть схитрил, предоставив доступ на чтение и запись к коллекции сообщений сразу всем посетителям сайта. Такого делать крайне не рекомендуется, ведь тогда через data-fiber любой поситель может перезаписать все сообщения на сервере чем-нибудь своим или вообще их стереть. Data-fibers стоит использовать только для связи пользовательских объектов на стороне браузера только с теми объектами данных на стороне сервера, которые принадлежат тому же пользователю. Это значит, никаких static моделей и данных!
Как нам исправить ситуацию? Для этого нам придётся:
- Отказаться от data-fiber, который работает по всем направлениям, а использовать его лишь для первоначальной загрузки списка сообщений.
- Использовать метод на стороне сервера добавления нового сообщения в список.
Таким образом, все смогут лишь добавлять новые сообщения в общий список, но не смогут ни удалять, ни менять уже существующие. Так же давайте чуть усложним серверную часть: будем сохранять не просто JSON, полученный от клиента, а уже специализированный класс Message с нужными полями, методами и т.д. для работы с данными на стороне сервера.
Начнем с класса Message для хранения сообщений пользователей. Это может быть, кстати, какой-то JPA класс, который позволяет сохранять данные в базу данных.
public class Message implements IClusterable { @JsonProperty("message") private String text; private Date date; public String getText() { return text; } public void setText(String text) { this.text = text; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } }
Обратите внимание на @JsonProperty. Таким образом мы перенаправили JSON поле «message» на наше Java поле «text».
Далее поменяем ChatApp.java чтобы сделать то, что описано выше: добавить vuecket метод для сохранения сообщения. Так же в коде можете заметить подрезание списка сообщений лишь до 20 (пользователи Хабра весьма старательные), но при удалении сообщения, оно всё равно сохраняется навечно в логах сервера.
@VueFile("ChatApp.vue") public class ChatApp extends VueComponent<Void> { private static final Logger LOG = LoggerFactory.getLogger(ChatApp.class); private static final int MAX_SIZE = 20; private static final IModel<List<Message>> MESSAGES = Model.ofList(new ArrayList<Message>()); public ChatApp(String id) { super(id); addDataFiber("messages", MESSAGES, true, false, false); } @VueMethod public synchronized void addMessage(Context ctx, Message message) { List<Message> list = MESSAGES.getObject(); list.add(message); trimToSize(list, MAX_SIZE); IVuecketMethod.pushDataPatch(ctx, "messages", list); } private void trimToSize(List<Message> list, int size) { //It's OK to delete one by one because in most of cases we will delete just one record while(list.size()>size) LOG.info("Bay-bay message: {}", list.remove(0)); } }
Видите метод с аннотацией @VueMethod? В нем мы получаем новое сообщение, сохраняем в списке, подрезаем и отправляем клиенту уже обновленный список. Так же обратите внимание, что data-fiber был переконфигурирован, чтобы запрашивать данные только при начальной загрузке Vue приложения.
Так же нам надо поменять логику в ChatApp.vue, чтобы вместо локального поля «messages» отправлять новое сообщение на сервер в ассинхронном режиме (vcInvoke)
module.exports = { data: function() { return { text : "", messages: [] } }, methods: { addMessage : function() { this.vcInvoke("addMessage", { message: this.text, date: new Date() }); this.text = ""; } } }
Что узнали из главы:
- Как создавать методы на стороне сервера для Vuecket’а
- Как вызывать методы на сервере из бразера
- Как отсылать клиенту нужные изменения
Заключение
На этом сегодня позвольте закруглиться. Есть еще много улучшений, которые можно придвнести в наш код, но я это оставлю вам, дорогие читатели. Если нужна будет помощь, то пишите, поможем!
Понравился фреймворк? Пожалуйста, поделитесь вашим мнением. Ведь именно вашим мнением живет и развивается Open Source.
- Поддержка WebSocket’ов для более реактивной связи клиента и сервера.
- Возможность пересылать между браузером и сервером именно дельту изменений, а не весь объект целиком.
- Больше настроек для data-fiber’ов для тонкой их настройки.
- Набор Vuecket/Wicket компонент, которые уже предварительно прединтегрированны с наиболее интересными VueJS библиотеками, например, уже упомянутый компонент для отображения текста в Markdown
ссылка на оригинал статьи https://habr.com/ru/company/orienteer/blog/514938/
Добавить комментарий